les: light client protocol and API

This commit is contained in:
zsfelfoldi 2015-12-23 00:15:12 +01:00
parent 301a0aaf8b
commit 4de92570cc
39 changed files with 4617 additions and 117 deletions

View file

@ -157,6 +157,9 @@ participating.
utils.BlockchainVersionFlag, utils.BlockchainVersionFlag,
utils.OlympicFlag, utils.OlympicFlag,
utils.FastSyncFlag, utils.FastSyncFlag,
utils.LightModeFlag,
utils.LightServFlag,
utils.LightPeersFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightKDFFlag, utils.LightKDFFlag,
utils.JSpathFlag, utils.JSpathFlag,

View file

@ -71,6 +71,9 @@ var AppHelpFlagGroups = []flagGroup{
utils.GenesisFileFlag, utils.GenesisFileFlag,
utils.IdentityFlag, utils.IdentityFlag,
utils.FastSyncFlag, utils.FastSyncFlag,
utils.LightModeFlag,
utils.LightServFlag,
utils.LightPeersFlag,
utils.LightKDFFlag, utils.LightKDFFlag,
utils.CacheFlag, utils.CacheFlag,
utils.BlockchainVersionFlag, utils.BlockchainVersionFlag,

View file

@ -39,6 +39,8 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -157,6 +159,20 @@ var (
Name: "fast", Name: "fast",
Usage: "Enable fast syncing through state downloads", Usage: "Enable fast syncing through state downloads",
} }
LightModeFlag = cli.BoolFlag{
Name: "light",
Usage: "Enable light client mode",
}
LightServFlag = cli.IntFlag{
Name: "lightserv",
Usage: "Maximum percentage of time allowed for serving LES requests (0-90)",
Value: 20,
}
LightPeersFlag = cli.IntFlag{
Name: "lightpeers",
Usage: "Maximum number of LES client peers",
Value: 10,
}
LightKDFFlag = cli.BoolFlag{ LightKDFFlag = cli.BoolFlag{
Name: "lightkdf", Name: "lightkdf",
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
@ -691,6 +707,9 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
ChainConfig: MustMakeChainConfig(ctx), ChainConfig: MustMakeChainConfig(ctx),
Genesis: MakeGenesisBlock(ctx), Genesis: MakeGenesisBlock(ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name), FastSync: ctx.GlobalBool(FastSyncFlag.Name),
LightMode: ctx.GlobalBool(LightModeFlag.Name),
LightServ: ctx.GlobalInt(LightServFlag.Name),
LightPeers: ctx.GlobalInt(LightPeersFlag.Name),
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name), DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
DatabaseHandles: MakeDatabaseHandles(), DatabaseHandles: MakeDatabaseHandles(),
@ -734,6 +753,7 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
ethConf.Genesis = core.TestNetGenesisBlock() ethConf.Genesis = core.TestNetGenesisBlock()
} }
state.StartingNonce = 1048576 // (2**20) state.StartingNonce = 1048576 // (2**20)
light.StartingNonce = 1048576 // (2**20)
case ctx.GlobalBool(DevModeFlag.Name): case ctx.GlobalBool(DevModeFlag.Name):
// Override the base network stack configs // Override the base network stack configs
@ -770,10 +790,23 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
Fatalf("Failed to register the account manager service: %v", err) Fatalf("Failed to register the account manager service: %v", err)
} }
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if ethConf.LightMode {
return eth.New(ctx, ethConf) if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
}); err != nil { return les.New(ctx, ethConf)
Fatalf("Failed to register the Ethereum service: %v", err) }); err != nil {
Fatalf("Failed to register the Ethereum light node service: %v", err)
}
} else {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
fullNode, err := eth.New(ctx, ethConf)
if fullNode != nil {
ls, _ := les.NewLesServer(fullNode, ethConf)
fullNode.AddLesServer(ls)
}
return fullNode, err
}); err != nil {
Fatalf("Failed to register the Ethereum full node service: %v", err)
}
} }
if shhEnable { if shhEnable {
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil {

View file

@ -343,11 +343,11 @@ func WriteBody(db ethdb.Database, hash common.Hash, number uint64, body *types.B
if err != nil { if err != nil {
return err return err
} }
return WriteBodyRLP(db, hash, data) return WriteBodyRLP(db, hash, number, data)
} }
// WriteBodyRLP writes a serialized body of a block into the database. // WriteBodyRLP writes a serialized body of a block into the database.
func WriteBodyRLP(db ethdb.Database, hash common.Hash, rlp rlp.RawValue) error { func WriteBodyRLP(db ethdb.Database, hash common.Hash, number uint64, rlp rlp.RawValue) error {
key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
if err := db.Put(key, rlp); err != nil { if err := db.Put(key, rlp); err != nil {
glog.Fatalf("failed to store block body into database: %v", err) glog.Fatalf("failed to store block body into database: %v", err)

View file

@ -67,9 +67,12 @@ var (
type Config struct { type Config struct {
ChainConfig *core.ChainConfig // chain configuration ChainConfig *core.ChainConfig // chain configuration
NetworkId int // Network ID to use for selecting peers to connect to NetworkId int // Network ID to use for selecting peers to connect to
Genesis string // Genesis JSON to seed the chain database with Genesis string // Genesis JSON to seed the chain database with
FastSync bool // Enables the state download based fast synchronisation algorithm FastSync bool // Enables the state download based fast synchronisation algorithm
LightMode bool // Running in light client mode
LightServ int // Maximum percentage of time allowed for serving LES requests
LightPeers int // Maximum number of LES client peers
BlockChainVersion int BlockChainVersion int
SkipBcVersionCheck bool // e.g. blockchain export SkipBcVersionCheck bool // e.g. blockchain export
@ -103,6 +106,12 @@ type Config struct {
TestGenesisState ethdb.Database // Genesis state to seed the database with (testing only!) TestGenesisState ethdb.Database // Genesis state to seed the database with (testing only!)
} }
type LesServer interface {
Start()
Stop()
Protocols() []p2p.Protocol
}
// FullNodeService implements the Ethereum full node service. // FullNodeService implements the Ethereum full node service.
type FullNodeService struct { type FullNodeService struct {
chainConfig *core.ChainConfig chainConfig *core.ChainConfig
@ -114,6 +123,7 @@ type FullNodeService struct {
txMu sync.Mutex txMu sync.Mutex
blockchain *core.BlockChain blockchain *core.BlockChain
protocolManager *ProtocolManager protocolManager *ProtocolManager
ls LesServer
// DB interfaces // DB interfaces
chainDb ethdb.Database // Block chain database chainDb ethdb.Database // Block chain database
dappDb ethdb.Database // Dapp database dappDb ethdb.Database // Dapp database
@ -140,6 +150,10 @@ type FullNodeService struct {
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
} }
func (s *FullNodeService) AddLesServer(ls LesServer) {
s.ls = ls
}
// New creates a new FullNodeService object (including the // New creates a new FullNodeService object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*FullNodeService, error) { func New(ctx *node.ServiceContext, config *Config) (*FullNodeService, error) {
@ -397,7 +411,11 @@ func (s *FullNodeService) Downloader() *downloader.Downloader { return s.protoco
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured
// network protocols to start. // network protocols to start.
func (s *FullNodeService) Protocols() []p2p.Protocol { func (s *FullNodeService) Protocols() []p2p.Protocol {
return s.protocolManager.SubProtocols if s.ls == nil {
return s.protocolManager.SubProtocols
} else {
return append(s.protocolManager.SubProtocols, s.ls.Protocols()...)
}
} }
// Start implements node.Service, starting all internal goroutines needed by the // Start implements node.Service, starting all internal goroutines needed by the
@ -408,6 +426,9 @@ func (s *FullNodeService) Start(srvr *p2p.Server) error {
s.StartAutoDAG() s.StartAutoDAG()
} }
s.protocolManager.Start() s.protocolManager.Start()
if s.ls != nil {
s.ls.Start()
}
return nil return nil
} }
@ -419,6 +440,9 @@ func (s *FullNodeService) Stop() error {
} }
s.blockchain.Stop() s.blockchain.Stop()
s.protocolManager.Stop() s.protocolManager.Stop()
if s.ls != nil {
s.ls.Stop()
}
s.txPool.Stop() s.txPool.Stop()
s.miner.Stop() s.miner.Stop()
s.eventMux.Stop() s.eventMux.Stop()

View file

@ -50,8 +50,6 @@ func upgradeSequentialKeys(db ethdb.Database) (stopFn func()) {
return nil // empty database, nothing to do return nil // empty database, nothing to do
} }
glog.V(logger.Info).Infof("Upgrading chain database to use sequential keys")
stopChn := make(chan struct{}) stopChn := make(chan struct{})
stoppedChn := make(chan struct{}) stoppedChn := make(chan struct{})

View file

@ -172,13 +172,13 @@ type Downloader struct {
} }
// New creates a new downloader to fetch hashes and blocks from remote peers. // New creates a new downloader to fetch hashes and blocks from remote peers.
func New(stateDb ethdb.Database, mux *event.TypeMux, hasHeader headerCheckFn, hasBlockAndState blockAndStateCheckFn, func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, hasHeader headerCheckFn, hasBlockAndState blockAndStateCheckFn,
getHeader headerRetrievalFn, getBlock blockRetrievalFn, headHeader headHeaderRetrievalFn, headBlock headBlockRetrievalFn, getHeader headerRetrievalFn, getBlock blockRetrievalFn, headHeader headHeaderRetrievalFn, headBlock headBlockRetrievalFn,
headFastBlock headFastBlockRetrievalFn, commitHeadBlock headBlockCommitterFn, getTd tdRetrievalFn, insertHeaders headerChainInsertFn, headFastBlock headFastBlockRetrievalFn, commitHeadBlock headBlockCommitterFn, getTd tdRetrievalFn, insertHeaders headerChainInsertFn,
insertBlocks blockChainInsertFn, insertReceipts receiptChainInsertFn, rollback chainRollbackFn, dropPeer peerDropFn) *Downloader { insertBlocks blockChainInsertFn, insertReceipts receiptChainInsertFn, rollback chainRollbackFn, dropPeer peerDropFn) *Downloader {
dl := &Downloader{ dl := &Downloader{
mode: FullSync, mode: mode,
mux: mux, mux: mux,
queue: newQueue(stateDb), queue: newQueue(stateDb),
peers: newPeerSet(), peers: newPeerSet(),

View file

@ -172,7 +172,7 @@ func newTester() *downloadTester {
tester.stateDb, _ = ethdb.NewMemDatabase() tester.stateDb, _ = ethdb.NewMemDatabase()
tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00}) tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00})
tester.downloader = New(tester.stateDb, new(event.TypeMux), tester.hasHeader, tester.hasBlock, tester.getHeader, tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester.hasHeader, tester.hasBlock, tester.getHeader,
tester.getBlock, tester.headHeader, tester.headBlock, tester.headFastBlock, tester.commitHeadBlock, tester.getTd, tester.getBlock, tester.headHeader, tester.headBlock, tester.headFastBlock, tester.commitHeadBlock, tester.getTd,
tester.insertHeaders, tester.insertBlocks, tester.insertReceipts, tester.rollback, tester.dropPeer) tester.insertHeaders, tester.insertBlocks, tester.insertReceipts, tester.rollback, tester.dropPeer)

160
eth/gasprice/lightprice.go Normal file
View file

@ -0,0 +1,160 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package gasprice
import (
"math/big"
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/net/context"
)
const (
LpoAvgCount = 5
LpoMinCount = 3
LpoMaxBlocks = 20
LpoSelect = 50
LpoDefaultPrice = 20000000000
)
// LightPriceOracle recommends gas prices based on the content of recent
// blocks. Suitable for both light and full clients.
type LightPriceOracle struct {
backend ethapi.Backend
lastHead common.Hash
lastPrice *big.Int
cacheLock sync.RWMutex
fetchLock sync.Mutex
}
// NewLightPriceOracle returns a new oracle.
func NewLightPriceOracle(backend ethapi.Backend) *LightPriceOracle {
return &LightPriceOracle{
backend: backend,
lastPrice: big.NewInt(LpoDefaultPrice),
}
}
// SuggestPrice returns the recommended gas price.
func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
self.cacheLock.RLock()
lastHead := self.lastHead
lastPrice := self.lastPrice
self.cacheLock.RUnlock()
head := self.backend.HeaderByNumber(rpc.LatestBlockNumber)
headHash := head.Hash()
if headHash == lastHead {
return lastPrice, nil
}
self.fetchLock.Lock()
defer self.fetchLock.Unlock()
// try checking the cache again, maybe the last fetch fetched what we need
self.cacheLock.RLock()
lastHead = self.lastHead
lastPrice = self.lastPrice
self.cacheLock.RUnlock()
if headHash == lastHead {
return lastPrice, nil
}
blockNum := head.GetNumberU64()
chn := make(chan lpResult, LpoMaxBlocks)
sent := 0
exp := 0
var lps bigIntArray
for sent < LpoAvgCount && blockNum > 0 {
go self.getLowestPrice(ctx, blockNum, chn)
sent++
exp++
blockNum--
}
maxEmpty := LpoAvgCount - LpoMinCount
for exp > 0 {
res := <-chn
if res.err != nil {
return nil, res.err
}
exp--
if res.price != nil {
lps = append(lps, res.price)
} else {
if maxEmpty > 0 {
maxEmpty--
} else {
if blockNum > 0 && sent < LpoMaxBlocks {
go self.getLowestPrice(ctx, blockNum, chn)
sent++
exp++
blockNum--
}
}
}
}
price := lastPrice
if len(lps) > 0 {
sort.Sort(lps)
price = lps[(len(lps)-1)*LpoSelect/100]
}
self.cacheLock.Lock()
self.lastHead = headHash
self.lastPrice = price
self.cacheLock.Unlock()
return price, nil
}
type lpResult struct {
price *big.Int
err error
}
// getLowestPrice calculates the lowest transaction gas price in a given block
// and sends it to the result channel. If the block is empty, price is nil.
func (self *LightPriceOracle) getLowestPrice(ctx context.Context, blockNum uint64, chn chan lpResult) {
block, err := self.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil {
chn <- lpResult{nil, err}
return
}
txs := block.Transactions()
if len(txs) == 0 {
chn <- lpResult{nil, nil}
return
}
// find smallest gasPrice
minPrice := txs[0].GasPrice()
for i := 1; i < len(txs); i++ {
price := txs[i].GasPrice()
if price.Cmp(minPrice) < 0 {
minPrice = price
}
}
chn <- lpResult{minPrice, nil}
}
type bigIntArray []*big.Int
func (s bigIntArray) Len() int { return len(s) }
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View file

@ -152,7 +152,7 @@ func NewProtocolManager(config *core.ChainConfig, fastSync bool, networkId int,
return nil, errIncompatibleConfig return nil, errIncompatibleConfig
} }
// Construct the different synchronisation mechanisms // Construct the different synchronisation mechanisms
manager.downloader = downloader.New(chaindb, manager.eventMux, blockchain.HasHeader, blockchain.HasBlockAndState, blockchain.GetHeaderByHash, manager.downloader = downloader.New(downloader.FullSync, chaindb, manager.eventMux, blockchain.HasHeader, blockchain.HasBlockAndState, blockchain.GetHeaderByHash,
blockchain.GetBlockByHash, blockchain.CurrentHeader, blockchain.CurrentBlock, blockchain.CurrentFastBlock, blockchain.FastSyncCommitHead, blockchain.GetBlockByHash, blockchain.CurrentHeader, blockchain.CurrentBlock, blockchain.CurrentFastBlock, blockchain.FastSyncCommitHead,
blockchain.GetTdByHash, blockchain.InsertHeaderChain, manager.insertChain, blockchain.InsertReceiptChain, blockchain.Rollback, blockchain.GetTdByHash, blockchain.InsertHeaderChain, manager.insertChain, blockchain.InsertReceiptChain, blockchain.Rollback,
manager.removePeer) manager.removePeer)

144
les/api_backend.go Normal file
View file

@ -0,0 +1,144 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/light"
rpc "github.com/ethereum/go-ethereum/rpc"
"golang.org/x/net/context"
)
type LesApiBackend struct {
eth *LightNodeService
gpo *gasprice.LightPriceOracle
}
func (b *LesApiBackend) SetHead(number uint64) {
b.eth.blockchain.SetHead(number)
}
func (b *LesApiBackend) HeaderByNumber(blockNr rpc.BlockNumber) *types.Header {
if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber {
return b.eth.blockchain.CurrentHeader()
}
return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr))
}
func (b *LesApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
header := b.HeaderByNumber(blockNr)
if header == nil {
return nil, nil
}
return b.GetBlock(ctx, header.Hash())
}
func (b *LesApiBackend) StateAndHeaderByNumber(blockNr rpc.BlockNumber) (ethapi.State, *types.Header, error) {
header := b.HeaderByNumber(blockNr)
if header == nil {
return nil, nil, nil
}
return light.NewLightState(light.StateTrieID(header), b.eth.odr), header, nil
}
func (b *LesApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(ctx, blockHash)
}
func (b *LesApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
return light.GetBlockReceipts(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
}
func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash)
}
func (b *LesApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (vm.Environment, func() error, error) {
stateDb := state.(*light.LightState).Copy()
addr, _ := msg.From()
from, err := stateDb.GetOrNewStateObject(ctx, addr)
if err != nil {
return nil, nil, err
}
from.SetBalance(common.MaxBig)
env := light.NewEnv(ctx, stateDb, b.eth.chainConfig, b.eth.blockchain, msg, header, b.eth.chainConfig.VmConfig)
return env, env.Error, nil
}
func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
return b.eth.txPool.Add(ctx, signedTx)
}
func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash)
}
func (b *LesApiBackend) GetPoolTransactions() types.Transactions {
return b.eth.txPool.GetTransactions()
}
func (b *LesApiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction {
return b.eth.txPool.GetTransaction(txHash)
}
func (b *LesApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return b.eth.txPool.GetNonce(ctx, addr)
}
func (b *LesApiBackend) Stats() (pending int, queued int) {
return b.eth.txPool.Stats(), 0
}
func (b *LesApiBackend) TxPoolContent() (map[common.Address]map[uint64][]*types.Transaction, map[common.Address]map[uint64][]*types.Transaction) {
return make(map[common.Address]map[uint64][]*types.Transaction), make(map[common.Address]map[uint64][]*types.Transaction)
}
func (b *LesApiBackend) Downloader() *downloader.Downloader {
return b.eth.Downloader()
}
func (b *LesApiBackend) ProtocolVersion() int {
return b.eth.LesVersion() + 10000
}
func (b *LesApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx)
}
func (b *LesApiBackend) ChainDb() ethdb.Database {
return b.eth.chainDb
}
func (b *LesApiBackend) EventMux() *event.TypeMux {
return b.eth.eventMux
}
func (b *LesApiBackend) AccountManager() *accounts.Manager {
return b.eth.accountManager
}

188
les/backend.go Normal file
View file

@ -0,0 +1,188 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"errors"
"fmt"
"time"
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
rpc "github.com/ethereum/go-ethereum/rpc"
)
type LightNodeService struct {
odr *LesOdr
relay *LesTxRelay
chainConfig *core.ChainConfig
// Channel for shutting down the service
shutdownChan chan bool
// Handlers
txPool *light.TxPool
blockchain *light.LightChain
protocolManager *ProtocolManager
// DB interfaces
chainDb ethdb.Database // Block chain database
dappDb ethdb.Database // Dapp database
apiBackend *LesApiBackend
eventMux *event.TypeMux
pow *ethash.Ethash
httpclient *httpclient.HTTPClient
accountManager *accounts.Manager
solcPath string
solc *compiler.Solidity
NatSpec bool
PowTest bool
netVersionId int
netRPCService *ethapi.PublicNetAPI
}
func New(ctx *node.ServiceContext, config *eth.Config) (*LightNodeService, error) {
chainDb, dappDb, err := eth.CreateDBs(ctx, config)
if err != nil {
return nil, err
}
if err := eth.SetupGenesisBlock(&chainDb, config); err != nil {
return nil, err
}
pow, err := eth.CreatePoW(config)
if err != nil {
return nil, err
}
odr := NewLesOdr(chainDb)
relay := NewLesTxRelay()
eth := &LightNodeService{
odr: odr,
relay: relay,
chainDb: chainDb,
dappDb: dappDb,
eventMux: ctx.EventMux,
accountManager: config.AccountManager,
pow: pow,
shutdownChan: make(chan bool),
httpclient: httpclient.New(config.DocRoot),
netVersionId: config.NetworkId,
NatSpec: config.NatSpec,
PowTest: config.PowTest,
solcPath: config.SolcPath,
}
if config.ChainConfig == nil {
return nil, errors.New("missing chain config")
}
eth.chainConfig = config.ChainConfig
eth.chainConfig.VmConfig = vm.Config{
EnableJit: config.EnableJit,
ForceJit: config.ForceJit,
}
eth.blockchain, err = light.NewLightChain(odr, eth.chainConfig, eth.pow, eth.eventMux)
if err != nil {
if err == core.ErrNoGenesis {
return nil, fmt.Errorf(`Genesis block not found. Please supply a genesis block with the "--genesis /path/to/file" argument`)
}
return nil, err
}
eth.txPool = light.NewTxPool(eth.chainConfig, eth.eventMux, eth.blockchain, eth.relay)
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.LightMode, config.NetworkId, eth.eventMux, eth.pow, eth.blockchain, nil, chainDb, odr, relay); err != nil {
return nil, err
}
odr.removePeer = eth.protocolManager.removePeer
eth.apiBackend = &LesApiBackend{eth, nil}
eth.apiBackend.gpo = gasprice.NewLightPriceOracle(eth.apiBackend)
return eth, nil
}
// APIs returns the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *LightNodeService) APIs() []rpc.API {
return append(ethapi.GetAPIs(s.apiBackend, &s.solcPath, &s.solc), []rpc.API{
{
Namespace: "eth",
Version: "1.0",
Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux),
Public: true,
}, {
Namespace: "net",
Version: "1.0",
Service: s.netRPCService,
Public: true,
},
}...)
}
func (s *LightNodeService) ResetWithGenesisBlock(gb *types.Block) {
s.blockchain.ResetWithGenesisBlock(gb)
}
func (s *LightNodeService) BlockChain() *light.LightChain { return s.blockchain }
func (s *LightNodeService) TxPool() *light.TxPool { return s.txPool }
func (s *LightNodeService) LesVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *LightNodeService) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
// Protocols implements node.Service, returning all the currently configured
// network protocols to start.
func (s *LightNodeService) Protocols() []p2p.Protocol {
return s.protocolManager.SubProtocols
}
// Start implements node.Service, starting all internal goroutines needed by the
// Ethereum protocol implementation.
func (s *LightNodeService) Start(srvr *p2p.Server) error {
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.netVersionId)
s.protocolManager.Start()
return nil
}
// Stop implements node.Service, terminating all internal goroutines used by the
// Ethereum protocol.
func (s *LightNodeService) Stop() error {
s.odr.Stop()
s.blockchain.Stop()
s.protocolManager.Stop()
s.txPool.Stop()
s.eventMux.Stop()
time.Sleep(time.Millisecond * 200)
s.chainDb.Close()
s.dappDb.Close()
close(s.shutdownChan)
return nil
}

170
les/flowcontrol/control.go Normal file
View file

@ -0,0 +1,170 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package flowcontrol implements a client side flow control mechanism
package flowcontrol
import (
"sync"
"time"
)
const fcTimeConst = 1000000
type ServerParams struct {
BufLimit, MinRecharge uint64
}
type ClientNode struct {
params *ServerParams
bufValue uint64
lastTime int64
lock sync.Mutex
cm *ClientManager
cmNode *cmNode
}
func NewClientNode(cm *ClientManager, params *ServerParams) *ClientNode {
node := &ClientNode{
cm: cm,
params: params,
bufValue: params.BufLimit,
lastTime: getTime(),
}
node.cmNode = cm.addNode(node)
return node
}
func (peer *ClientNode) Remove(cm *ClientManager) {
cm.removeNode(peer.cmNode)
}
func (peer *ClientNode) recalcBV(time int64) {
dt := uint64(time - peer.lastTime)
if time < peer.lastTime {
dt = 0
}
peer.bufValue += peer.params.MinRecharge * dt / fcTimeConst
if peer.bufValue > peer.params.BufLimit {
peer.bufValue = peer.params.BufLimit
}
peer.lastTime = time
}
func (peer *ClientNode) AcceptRequest() (uint64, bool) {
peer.lock.Lock()
defer peer.lock.Unlock()
time := getTime()
peer.recalcBV(time)
return peer.bufValue, peer.cm.accept(peer.cmNode, time)
}
func (peer *ClientNode) RequestProcessed(cost uint64) (bv, realCost uint64) {
peer.lock.Lock()
defer peer.lock.Unlock()
time := getTime()
peer.recalcBV(getTime())
peer.bufValue -= cost
peer.recalcBV(time)
rcValue, rcost := peer.cm.processed(peer.cmNode, time)
if rcValue < peer.params.BufLimit {
bv := peer.params.BufLimit - rcValue
if bv > peer.bufValue {
peer.bufValue = bv
}
}
return peer.bufValue, rcost
}
type ServerNode struct {
bufEstimate uint64
lastTime int64
params *ServerParams
sumCost uint64 // sum of req costs sent to this server
pending map[uint64]uint64 // value = sumCost after sending the given req
lock sync.Mutex
}
func NewServerNode(params *ServerParams) *ServerNode {
return &ServerNode{
bufEstimate: params.BufLimit,
lastTime: getTime(),
params: params,
pending: make(map[uint64]uint64),
}
}
func getTime() int64 {
return time.Now().UnixNano()
}
func (peer *ServerNode) recalcBLE(time int64) {
dt := uint64(time - peer.lastTime)
if time < peer.lastTime {
dt = 0
}
peer.bufEstimate += peer.params.MinRecharge * dt / fcTimeConst
if peer.bufEstimate > peer.params.BufLimit {
peer.bufEstimate = peer.params.BufLimit
}
peer.lastTime = time
}
func (peer *ServerNode) canSend(maxCost uint64) uint64 {
if peer.bufEstimate >= maxCost {
return 0
}
return (maxCost - peer.bufEstimate) * fcTimeConst / peer.params.MinRecharge
}
func (peer *ServerNode) CanSend(maxCost uint64) uint64 {
peer.lock.Lock()
defer peer.lock.Unlock()
return peer.canSend(maxCost)
}
// blocks until request can be sent
func (peer *ServerNode) SendRequest(reqID, maxCost uint64) {
peer.lock.Lock()
defer peer.lock.Unlock()
peer.recalcBLE(getTime())
for peer.bufEstimate < maxCost {
time.Sleep(time.Duration(peer.canSend(maxCost)))
peer.recalcBLE(getTime())
}
peer.bufEstimate -= maxCost
peer.sumCost += maxCost
if reqID >= 0 {
peer.pending[reqID] = peer.sumCost
}
}
func (peer *ServerNode) GotReply(reqID, bv uint64) {
peer.lock.Lock()
defer peer.lock.Unlock()
sc, ok := peer.pending[reqID]
if !ok {
return
}
delete(peer.pending, reqID)
peer.bufEstimate = bv - (peer.sumCost - sc)
peer.lastTime = getTime()
}

223
les/flowcontrol/manager.go Normal file
View file

@ -0,0 +1,223 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package flowcontrol implements a client side flow control mechanism
package flowcontrol
import (
"sync"
"time"
)
const rcConst = 1000000
type cmNode struct {
node *ClientNode
lastUpdate int64
reqAccepted int64
serving, recharging bool
rcWeight uint64
rcValue, rcDelta int64
finishRecharge, startValue int64
}
func (node *cmNode) update(time int64) {
dt := time - node.lastUpdate
node.rcValue += node.rcDelta * dt / rcConst
node.lastUpdate = time
if node.recharging && time >= node.finishRecharge {
node.recharging = false
node.rcDelta = 0
node.rcValue = 0
}
}
func (node *cmNode) set(serving bool, simReqCnt, sumWeight uint64) {
if node.serving && !serving {
node.recharging = true
sumWeight += node.rcWeight
}
node.serving = serving
if node.recharging && serving {
node.recharging = false
sumWeight -= node.rcWeight
}
node.rcDelta = 0
if serving {
node.rcDelta = int64(rcConst / simReqCnt)
}
if node.recharging {
node.rcDelta = -int64(node.node.cm.rcRecharge * node.rcWeight / sumWeight)
node.finishRecharge = node.lastUpdate + node.rcValue*rcConst/(-node.rcDelta)
}
}
type ClientManager struct {
lock sync.Mutex
nodes map[*cmNode]struct{}
simReqCnt, sumWeight, rcSumValue uint64
maxSimReq, maxRcSum uint64
rcRecharge uint64
resumeQueue chan chan bool
time int64
}
func NewClientManager(rcTarget, maxSimReq, maxRcSum uint64) *ClientManager {
cm := &ClientManager{
nodes: make(map[*cmNode]struct{}),
resumeQueue: make(chan chan bool),
rcRecharge: rcConst * rcConst / (100*rcConst/rcTarget - rcConst),
maxSimReq: maxSimReq,
maxRcSum: maxRcSum,
}
go cm.queueProc()
return cm
}
func (self *ClientManager) Stop() {
self.lock.Lock()
defer self.lock.Unlock()
// signal any waiting accept routines to return false
self.nodes = make(map[*cmNode]struct{})
close(self.resumeQueue)
}
func (self *ClientManager) addNode(cnode *ClientNode) *cmNode {
time := getTime()
node := &cmNode{
node: cnode,
lastUpdate: time,
finishRecharge: time,
rcWeight: 1,
}
self.lock.Lock()
defer self.lock.Unlock()
self.nodes[node] = struct{}{}
self.update(getTime())
return node
}
func (self *ClientManager) removeNode(node *cmNode) {
self.lock.Lock()
defer self.lock.Unlock()
time := getTime()
self.stop(node, time)
delete(self.nodes, node)
self.update(time)
}
// recalc sumWeight
func (self *ClientManager) updateNodes(time int64) (rce bool) {
var sumWeight, rcSum uint64
for node, _ := range self.nodes {
rc := node.recharging
node.update(time)
if rc && !node.recharging {
rce = true
}
if node.recharging {
sumWeight += node.rcWeight
}
rcSum += uint64(node.rcValue)
}
self.sumWeight = sumWeight
self.rcSumValue = rcSum
return
}
func (self *ClientManager) update(time int64) {
for {
firstTime := time
for node, _ := range self.nodes {
if node.recharging && node.finishRecharge < firstTime {
firstTime = node.finishRecharge
}
}
if self.updateNodes(firstTime) {
for node, _ := range self.nodes {
if node.recharging {
node.set(node.serving, self.simReqCnt, self.sumWeight)
}
}
} else {
self.time = time
return
}
}
}
func (self *ClientManager) canStartReq() bool {
return self.simReqCnt < self.maxSimReq && self.rcSumValue < self.maxRcSum
}
func (self *ClientManager) queueProc() {
for rc := range self.resumeQueue {
for {
time.Sleep(time.Millisecond * 10)
self.lock.Lock()
self.update(getTime())
cs := self.canStartReq()
self.lock.Unlock()
if cs {
break
}
}
close(rc)
}
}
func (self *ClientManager) accept(node *cmNode, time int64) bool {
self.lock.Lock()
defer self.lock.Unlock()
self.update(time)
if !self.canStartReq() {
resume := make(chan bool)
self.resumeQueue <- resume
self.lock.Unlock()
<-resume
self.lock.Lock()
if _, ok := self.nodes[node]; !ok {
return false // reject if node has been removed or manager has been stopped
}
}
self.simReqCnt++
node.set(true, self.simReqCnt, self.sumWeight)
node.startValue = node.rcValue
self.update(self.time)
return true
}
func (self *ClientManager) stop(node *cmNode, time int64) {
if node.serving {
self.update(time)
self.simReqCnt--
node.set(false, self.simReqCnt, self.sumWeight)
self.update(time)
}
}
func (self *ClientManager) processed(node *cmNode, time int64) (rcValue, rcCost uint64) {
self.lock.Lock()
defer self.lock.Unlock()
self.stop(node, time)
return uint64(node.rcValue), uint64(node.rcValue - node.startValue)
}

736
les/handler.go Normal file
View file

@ -0,0 +1,736 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"errors"
"fmt"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/pow"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
const (
softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
ethVersion = 63 // equivalent eth version for the downloader
MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
)
// errIncompatibleConfig is returned if the requested protocols and configs are
// not compatible (low protocol version restrictions and high requirements).
var errIncompatibleConfig = errors.New("incompatible configuration")
func errResp(code errCode, format string, v ...interface{}) error {
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
}
type hashFetcherFn func(common.Hash) error
type BlockChain interface {
HasHeader(hash common.Hash) bool
GetHeader(hash common.Hash, number uint64) *types.Header
GetHeaderByHash(hash common.Hash) *types.Header
CurrentHeader() *types.Header
GetTdByHash(hash common.Hash) *big.Int
InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error)
Rollback(chain []common.Hash)
Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash)
GetHeaderByNumber(number uint64) *types.Header
GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash
LastBlockHash() common.Hash
Genesis() *types.Block
}
type txPool interface {
// AddTransactions should add the given transactions to the pool.
AddTransactions([]*types.Transaction)
}
type ProtocolManager struct {
lightSync bool
txpool txPool
txrelay *LesTxRelay
networkId int
chainConfig *core.ChainConfig
blockchain BlockChain
chainDb ethdb.Database
odr *LesOdr
server *LesServer
downloader *downloader.Downloader
//fetcher *fetcher.Fetcher
peers *peerSet
SubProtocols []p2p.Protocol
eventMux *event.TypeMux
// channels for fetcher, syncer, txsyncLoop
newPeerCh chan *peer
quitSync chan struct{}
noMorePeers chan struct{}
// wait group is used for graceful shutdowns during downloading
// and processing
wg sync.WaitGroup
}
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network.
func NewProtocolManager(chainConfig *core.ChainConfig, lightSync bool, networkId int, mux *event.TypeMux, pow pow.PoW, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay) (*ProtocolManager, error) {
// Create the protocol manager with the base fields
manager := &ProtocolManager{
lightSync: lightSync,
eventMux: mux,
blockchain: blockchain,
chainConfig: chainConfig,
chainDb: chainDb,
networkId: networkId,
txpool: txpool,
txrelay: txrelay,
odr: odr,
peers: newPeerSet(),
newPeerCh: make(chan *peer, 1),
quitSync: make(chan struct{}),
noMorePeers: make(chan struct{}),
}
// Initiate a sub-protocol for every implemented version we can handle
manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions))
for i, version := range ProtocolVersions {
// Compatible, initialize the sub-protocol
version := version // Closure for the run
manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
Name: "les",
Version: version,
Length: ProtocolLengths[i],
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := manager.newPeer(int(version), networkId, p, rw)
select {
case manager.newPeerCh <- peer:
manager.wg.Add(1)
defer manager.wg.Done()
return manager.handle(peer)
case <-manager.quitSync:
return p2p.DiscQuitting
}
},
NodeInfo: func() interface{} {
return manager.NodeInfo()
},
PeerInfo: func(id discover.NodeID) interface{} {
if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
return p.Info()
}
return nil
},
})
}
if len(manager.SubProtocols) == 0 {
return nil, errIncompatibleConfig
}
if lightSync {
glog.V(logger.Debug).Infof("LES: create downloader")
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, blockchain.HasHeader, nil, blockchain.GetHeaderByHash,
nil, blockchain.CurrentHeader, nil, nil, nil, blockchain.GetTdByHash,
blockchain.InsertHeaderChain, nil, nil, blockchain.Rollback, manager.removePeer)
}
/*validator := func(block *types.Block, parent *types.Block) error {
return core.ValidateHeader(pow, block.Header(), parent.Header(), true, false)
}
heighter := func() uint64 {
return chainman.LastBlockNumberU64()
}
manager.fetcher = fetcher.New(chainman.GetBlockNoOdr, validator, nil, heighter, chainman.InsertChain, manager.removePeer)
*/
return manager, nil
}
func (pm *ProtocolManager) removePeer(id string) {
// Short circuit if the peer was already removed
peer := pm.peers.Peer(id)
if peer == nil {
return
}
glog.V(logger.Debug).Infoln("Removing peer", id)
// Unregister the peer from the downloader and Ethereum peer set
glog.V(logger.Debug).Infof("LES: unregister peer %v", id)
if pm.lightSync {
pm.downloader.UnregisterPeer(id)
pm.odr.UnregisterPeer(peer)
if pm.txrelay != nil {
pm.txrelay.removePeer(id)
}
}
if err := pm.peers.Unregister(id); err != nil {
glog.V(logger.Error).Infoln("Removal failed:", err)
}
// Hard disconnect at the networking layer
if peer != nil {
peer.Peer.Disconnect(p2p.DiscUselessPeer)
}
}
func (pm *ProtocolManager) Start() {
if pm.lightSync {
// start sync handler
go pm.syncer()
}
}
func (pm *ProtocolManager) Stop() {
// Showing a log message. During download / process this could actually
// take between 5 to 10 seconds and therefor feedback is required.
glog.V(logger.Info).Infoln("Stopping light ethereum protocol handler...")
// Quit the sync loop.
// After this send has completed, no new peers will be accepted.
pm.noMorePeers <- struct{}{}
close(pm.quitSync) // quits syncer, fetcher
// Disconnect existing sessions.
// This also closes the gate for any new registrations on the peer set.
// sessions which are already established but not added to pm.peers yet
// will exit when they try to register.
pm.peers.Close()
// Wait for any process action
pm.wg.Wait()
glog.V(logger.Info).Infoln("Light ethereum protocol handler stopped")
}
func (pm *ProtocolManager) newPeer(pv, nv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
return newPeer(pv, nv, p, newMeteredMsgWriter(rw))
}
// handle is the callback invoked to manage the life cycle of a les peer. When
// this function terminates, the peer is disconnected.
func (pm *ProtocolManager) handle(p *peer) error {
glog.V(logger.Debug).Infof("%v: peer connected [%s]", p, p.Name())
// Execute the LES handshake
td, head, genesis := pm.blockchain.Status()
if err := p.Handshake(td, head, genesis, pm.server); err != nil {
glog.V(logger.Debug).Infof("%v: handshake failed: %v", p, err)
return err
}
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
rw.Init(p.version)
}
// Register the peer locally
glog.V(logger.Detail).Infof("%v: adding peer", p)
if err := pm.peers.Register(p); err != nil {
glog.V(logger.Error).Infof("%v: addition failed: %v", p, err)
return err
}
defer func() {
if pm.server != nil && pm.server.fcManager != nil && p.fcClient != nil {
p.fcClient.Remove(pm.server.fcManager)
}
pm.removePeer(p.id)
}()
// Register the peer in the downloader. If the downloader considers it banned, we disconnect
glog.V(logger.Debug).Infof("LES: register peer %v", p.id)
if pm.lightSync {
requestHeadersByHash := func(origin common.Hash, amount int, skip int, reverse bool) error {
reqID := pm.odr.getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost)
return p.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse)
}
requestHeadersByNumber := func(origin uint64, amount int, skip int, reverse bool) error {
reqID := pm.odr.getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost)
return p.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse)
}
if err := pm.downloader.RegisterPeer(p.id, ethVersion, p.Head(),
nil, nil, nil, requestHeadersByHash, requestHeadersByNumber,
nil, nil, nil); err != nil {
return err
}
pm.odr.RegisterPeer(p)
if pm.txrelay != nil {
pm.txrelay.addPeer(p)
}
}
// main loop. handle incoming messages.
for {
if err := pm.handleMsg(p); err != nil {
glog.V(logger.Debug).Infof("%v: message handling failed: %v", p, err)
return err
}
}
return nil
}
var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsMsg, SendTxMsg}
// handleMsg is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func (pm *ProtocolManager) handleMsg(p *peer) error {
// Read the next message from the remote peer, and ensure it's fully consumed
msg, err := p.rw.ReadMsg()
if err != nil {
return err
}
var costs *requestCosts
var reqCnt, maxReqs int
if rc, ok := p.fcCosts[msg.Code]; ok { // check if msg is a supported request type
costs = rc
if p.fcClient == nil {
return errResp(ErrRequestRejected, "")
}
bv, ok := p.fcClient.AcceptRequest()
if !ok || bv < costs.baseCost {
return errResp(ErrRequestRejected, "")
}
d := bv - costs.baseCost
if d/10000 < costs.reqCost {
maxReqs = int(d / costs.reqCost)
} else {
maxReqs = 10000
}
}
if msg.Size > ProtocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
}
defer msg.Discard()
var deliverMsg *Msg
// Handle the message depending on its contents
switch msg.Code {
case StatusMsg:
glog.V(logger.Debug).Infof("LES: received StatusMsg from peer %v", p.id)
// Status messages should never arrive after the handshake
return errResp(ErrExtraStatusMsg, "uncontrolled status message")
// Block header query, collect the requested headers and reply
case GetBlockHeadersMsg:
glog.V(logger.Debug).Infof("LES: received GetBlockHeadersMsg from peer %v", p.id)
// Decode the complex header query
var req struct {
ReqID uint64
Query getBlockHeadersData
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err)
}
query := req.Query
hashMode := query.Origin.Hash != (common.Hash{})
// Gather headers until the fetch or network limits is reached
var (
bytes common.StorageSize
headers []*types.Header
unknown bool
)
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch {
// Retrieve the next header satisfying the query
var origin *types.Header
if hashMode {
origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
} else {
origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
}
if origin == nil {
break
}
number := origin.Number.Uint64()
headers = append(headers, origin)
bytes += estHeaderRlpSize
// Advance to the next header of the query
switch {
case query.Origin.Hash != (common.Hash{}) && query.Reverse:
// Hash based traversal towards the genesis block
for i := 0; i < int(query.Skip)+1; i++ {
if header := pm.blockchain.GetHeader(query.Origin.Hash, number); header != nil {
query.Origin.Hash = header.ParentHash
number--
} else {
unknown = true
break
}
}
case query.Origin.Hash != (common.Hash{}) && !query.Reverse:
// Hash based traversal towards the leaf block
if header := pm.blockchain.GetHeaderByNumber(origin.Number.Uint64() + query.Skip + 1); header != nil {
if pm.blockchain.GetBlockHashesFromHash(header.Hash(), query.Skip+1)[query.Skip] == query.Origin.Hash {
query.Origin.Hash = header.Hash()
} else {
unknown = true
}
} else {
unknown = true
}
case query.Reverse:
// Number based traversal towards the genesis block
if query.Origin.Number >= query.Skip+1 {
query.Origin.Number -= (query.Skip + 1)
} else {
unknown = true
}
case !query.Reverse:
// Number based traversal towards the leaf block
query.Origin.Number += (query.Skip + 1)
}
}
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + query.Amount * costs.reqCost)
pm.server.fcCostStats.update(msg.Code, query.Amount, rcost)
return p.SendBlockHeaders(req.ReqID, bv, headers)
case BlockHeadersMsg:
if pm.downloader == nil {
return errResp(ErrUnexpectedResponse, "")
}
glog.V(logger.Debug).Infof("LES: received BlockHeadersMsg from peer %v", p.id)
// A batch of headers arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Headers []*types.Header
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
err := pm.downloader.DeliverHeaders(p.id, resp.Headers)
if err != nil {
glog.V(logger.Debug).Infoln(err)
}
case GetBlockBodiesMsg:
glog.V(logger.Debug).Infof("LES: received GetBlockBodiesMsg from peer %v", p.id)
// Decode the retrieval message
var req struct {
ReqID uint64
Hashes []common.Hash
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather blocks until the fetch or network limits is reached
var (
bytes int
bodies []rlp.RawValue
)
reqCnt = len(req.Hashes)
if reqCnt > maxReqs {
return errResp(ErrRequestRejected, "")
}
for _, hash := range req.Hashes {
if bytes >= softResponseLimit || len(bodies) >= MaxBodyFetch {
break
}
// Retrieve the requested block body, stopping if enough was found
if data := core.GetBodyRLP(pm.chainDb, hash, core.GetBlockNumber(pm.chainDb, hash)); len(data) != 0 {
bodies = append(bodies, data)
bytes += len(data)
}
}
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendBlockBodiesRLP(req.ReqID, bv, bodies)
case BlockBodiesMsg:
if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "")
}
glog.V(logger.Debug).Infof("LES: received BlockBodiesMsg from peer %v", p.id)
// A batch of block bodies arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Data []*types.Body
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgBlockBodies,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case GetCodeMsg:
glog.V(logger.Debug).Infof("LES: received GetCodeMsg from peer %v", p.id)
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs []CodeReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
bytes int
data [][]byte
)
reqCnt = len(req.Reqs)
if reqCnt > maxReqs {
return errResp(ErrRequestRejected, "")
}
for _, req := range req.Reqs {
if len(data) >= MaxCodeFetch {
break
}
// Retrieve the requested state entry, stopping if enough was found
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
if trie, _ := trie.New(header.Root, pm.chainDb); trie != nil {
sdata := trie.Get(req.AccKey)
if so, err := state.DecodeObject(common.Address{}, pm.chainDb, sdata); err == nil {
entry := so.Code()
if bytes+len(entry) >= softResponseLimit {
break
}
data = append(data, entry)
bytes += len(entry)
}
}
}
}
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendCode(req.ReqID, bv, data)
case CodeMsg:
if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "")
}
glog.V(logger.Debug).Infof("LES: received CodeMsg from peer %v", p.id)
// A batch of node state data arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Data [][]byte
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgCode,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case GetReceiptsMsg:
glog.V(logger.Debug).Infof("LES: received GetReceiptsMsg from peer %v", p.id)
// Decode the retrieval message
var req struct {
ReqID uint64
Hashes []common.Hash
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
bytes int
receipts []rlp.RawValue
)
reqCnt = len(req.Hashes)
if reqCnt > maxReqs {
return errResp(ErrRequestRejected, "")
}
for _, hash := range req.Hashes {
if bytes >= softResponseLimit || len(receipts) >= MaxReceiptFetch {
break
}
// Retrieve the requested block's receipts, skipping if unknown to us
results := core.GetBlockReceipts(pm.chainDb, hash, core.GetBlockNumber(pm.chainDb, hash))
if results == nil {
if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
continue
}
}
// If known, encode and queue for response packet
if encoded, err := rlp.EncodeToBytes(results); err != nil {
glog.V(logger.Error).Infof("failed to encode receipt: %v", err)
} else {
receipts = append(receipts, encoded)
bytes += len(encoded)
}
}
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendReceiptsRLP(req.ReqID, bv, receipts)
case ReceiptsMsg:
if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "")
}
glog.V(logger.Debug).Infof("LES: received ReceiptsMsg from peer %v", p.id)
// A batch of receipts arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Receipts []types.Receipts
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgReceipts,
ReqID: resp.ReqID,
Obj: resp.Receipts,
}
case GetProofsMsg:
glog.V(logger.Debug).Infof("LES: received GetProofsMsg from peer %v", p.id)
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs []ProofReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
bytes int
proofs proofsData
)
reqCnt = len(req.Reqs)
if reqCnt > maxReqs {
return errResp(ErrRequestRejected, "")
}
for _, req := range req.Reqs {
if bytes >= softResponseLimit || len(proofs) >= MaxProofsFetch {
break
}
// Retrieve the requested state entry, stopping if enough was found
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
if tr, _ := trie.New(header.Root, pm.chainDb); tr != nil {
if len(req.AccKey) > 0 {
data := tr.Get(req.AccKey)
tr = nil
if so, err := state.DecodeObject(common.Address{}, pm.chainDb, data); err == nil {
tr, _ = trie.New(common.BytesToHash(so.Root()), pm.chainDb)
}
}
if tr != nil {
proof := tr.Prove(req.Key)
proofs = append(proofs, proof)
bytes += len(proof)
}
}
}
}
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendProofs(req.ReqID, bv, proofs)
case ProofsMsg:
if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "")
}
glog.V(logger.Debug).Infof("LES: received ProofsMsg from peer %v", p.id)
// A batch of merkle proofs arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Data [][]rlp.RawValue
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgProofs,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case SendTxMsg:
if pm.txpool == nil {
return errResp(ErrUnexpectedResponse, "")
}
// Transactions arrived, parse all of them and deliver to the pool
var txs []*types.Transaction
if err := msg.Decode(&txs); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt = len(txs)
if reqCnt > maxReqs {
return errResp(ErrRequestRejected, "")
}
pm.txpool.AddTransactions(txs)
_, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
default:
glog.V(logger.Debug).Infof("LES: received unknown message with code %d from peer %v", msg.Code, p.id)
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
}
if deliverMsg != nil {
return pm.odr.Deliver(p, deliverMsg)
}
return nil
}
// NodeInfo retrieves some protocol metadata about the running host node.
func (self *ProtocolManager) NodeInfo() *eth.EthNodeInfo {
return &eth.EthNodeInfo{
Network: self.networkId,
Difficulty: self.blockchain.GetTdByHash(self.blockchain.LastBlockHash()),
Genesis: self.blockchain.Genesis().Hash(),
Head: self.blockchain.LastBlockHash(),
}
}

322
les/handler_test.go Normal file
View file

@ -0,0 +1,322 @@
package les
import (
"math/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}) error {
type resp struct {
ReqID, BV uint64
Data interface{}
}
return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data})
}
// Tests that block headers can be retrieved from a remote chain based on user queries.
func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) }
func testGetBlockHeaders(t *testing.T, protocol int) {
pm, _, _ := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil)
bc := pm.blockchain.(*core.BlockChain)
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
defer peer.close()
// Create a "random" unknown hash for testing
var unknown common.Hash
for i, _ := range unknown {
unknown[i] = byte(i)
}
// Create a batch of tests for various scenarios
limit := uint64(MaxHeaderFetch)
tests := []struct {
query *getBlockHeadersData // The query to execute for header retrieval
expect []common.Hash // The hashes of the block whose headers are expected
}{
// A single random block should be retrievable by hash and number too
{
&getBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
[]common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
[]common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
},
// Multiple headers should be retrievable in both directions
{
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
[]common.Hash{
bc.GetBlockByNumber(limit / 2).Hash(),
bc.GetBlockByNumber(limit/2 + 1).Hash(),
bc.GetBlockByNumber(limit/2 + 2).Hash(),
},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
[]common.Hash{
bc.GetBlockByNumber(limit / 2).Hash(),
bc.GetBlockByNumber(limit/2 - 1).Hash(),
bc.GetBlockByNumber(limit/2 - 2).Hash(),
},
},
// Multiple headers with skip lists should be retrievable
{
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
[]common.Hash{
bc.GetBlockByNumber(limit / 2).Hash(),
bc.GetBlockByNumber(limit/2 + 4).Hash(),
bc.GetBlockByNumber(limit/2 + 8).Hash(),
},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
[]common.Hash{
bc.GetBlockByNumber(limit / 2).Hash(),
bc.GetBlockByNumber(limit/2 - 4).Hash(),
bc.GetBlockByNumber(limit/2 - 8).Hash(),
},
},
// The chain endpoints should be retrievable
{
&getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
[]common.Hash{bc.GetBlockByNumber(0).Hash()},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1},
[]common.Hash{bc.CurrentBlock().Hash()},
},
// Ensure protocol limits are honored
{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
bc.GetBlockHashesFromHash(bc.CurrentBlock().Hash(), limit),
},
// Check that requesting more than available is handled gracefully
{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
[]common.Hash{
bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(),
bc.GetBlockByNumber(bc.CurrentBlock().NumberU64()).Hash(),
},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
[]common.Hash{
bc.GetBlockByNumber(4).Hash(),
bc.GetBlockByNumber(0).Hash(),
},
},
// Check that requesting more than available is handled gracefully, even if mid skip
{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
[]common.Hash{
bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(),
bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1).Hash(),
},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
[]common.Hash{
bc.GetBlockByNumber(4).Hash(),
bc.GetBlockByNumber(1).Hash(),
},
},
// Check that non existing headers aren't returned
{
&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
[]common.Hash{},
}, {
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1},
[]common.Hash{},
},
}
// Run each of the tests and verify the results against the chain
var reqID uint64
for i, tt := range tests {
// Collect the headers to expect in the response
headers := []*types.Header{}
for _, hash := range tt.expect {
headers = append(headers, bc.GetHeaderByHash(hash))
}
// Send the hash request and verify the response
reqID++
cost := peer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount))
sendRequest(peer.app, GetBlockHeadersMsg, reqID, cost, tt.query)
if err := expectResponse(peer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
t.Errorf("test %d: headers mismatch: %v", i, err)
}
}
}
// Tests that block contents can be retrieved from a remote chain based on their hashes.
func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) }
func testGetBlockBodies(t *testing.T, protocol int) {
pm, _, _ := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil)
bc := pm.blockchain.(*core.BlockChain)
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
defer peer.close()
// Create a batch of tests for various scenarios
limit := MaxBodyFetch
tests := []struct {
random int // Number of blocks to fetch randomly from the chain
explicit []common.Hash // Explicitly requested blocks
available []bool // Availability of explicitly requested blocks
expected int // Total number of existing blocks to expect
}{
{1, nil, nil, 1}, // A single random block should be retrievable
{10, nil, nil, 10}, // Multiple random blocks should be retrievable
{limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
{0, []common.Hash{bc.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
{0, []common.Hash{bc.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
{0, []common.Hash{common.Hash{}}, []bool{false}, 0}, // A non existent block should not be returned
// Existing and non-existing blocks interleaved should not cause problems
{0, []common.Hash{
common.Hash{},
bc.GetBlockByNumber(1).Hash(),
common.Hash{},
bc.GetBlockByNumber(10).Hash(),
common.Hash{},
bc.GetBlockByNumber(100).Hash(),
common.Hash{},
}, []bool{false, true, false, true, false, true, false}, 3},
}
// Run each of the tests and verify the results against the chain
var reqID uint64
for i, tt := range tests {
// Collect the hashes to request, and the response to expect
hashes, seen := []common.Hash{}, make(map[int64]bool)
bodies := []*types.Body{}
for j := 0; j < tt.random; j++ {
for {
num := rand.Int63n(int64(bc.CurrentBlock().NumberU64()))
if !seen[num] {
seen[num] = true
block := bc.GetBlockByNumber(uint64(num))
hashes = append(hashes, block.Hash())
if len(bodies) < tt.expected {
bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()})
}
break
}
}
}
for j, hash := range tt.explicit {
hashes = append(hashes, hash)
if tt.available[j] && len(bodies) < tt.expected {
block := bc.GetBlockByHash(hash)
bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()})
}
}
reqID++
// Send the hash request and verify the response
cost := peer.GetRequestCost(GetBlockBodiesMsg, len(hashes))
sendRequest(peer.app, GetBlockBodiesMsg, reqID, cost, hashes)
if err := expectResponse(peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
t.Errorf("test %d: bodies mismatch: %v", i, err)
}
}
}
// Tests that the contract codes can be retrieved based on account addresses.
func TestGetCodeLes1(t *testing.T) { testGetCode(t, 1) }
func testGetCode(t *testing.T, protocol int) {
// Assemble the test environment
pm, _, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
bc := pm.blockchain.(*core.BlockChain)
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
defer peer.close()
var codereqs []*CodeReq
var codes [][]byte
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
header := bc.GetHeaderByNumber(i)
req := &CodeReq{
BHash: header.Hash(),
AccKey: crypto.Keccak256(testContractAddr[:]),
}
codereqs = append(codereqs, req)
if i >= testContractDeployed {
codes = append(codes, testContractCodeDeployed)
}
}
cost := peer.GetRequestCost(GetCodeMsg, len(codereqs))
sendRequest(peer.app, GetCodeMsg, 42, cost, codereqs)
if err := expectResponse(peer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
t.Errorf("codes mismatch: %v", err)
}
}
// Tests that the transaction receipts can be retrieved based on hashes.
func TestGetReceiptLes1(t *testing.T) { testGetReceipt(t, 1) }
func testGetReceipt(t *testing.T, protocol int) {
// Assemble the test environment
pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
bc := pm.blockchain.(*core.BlockChain)
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
defer peer.close()
// Collect the hashes to request, and the response to expect
hashes, receipts := []common.Hash{}, []types.Receipts{}
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
block := bc.GetBlockByNumber(i)
hashes = append(hashes, block.Hash())
receipts = append(receipts, core.GetBlockReceipts(db, block.Hash(), block.NumberU64()))
}
// Send the hash request and verify the response
cost := peer.GetRequestCost(GetReceiptsMsg, len(hashes))
sendRequest(peer.app, GetReceiptsMsg, 42, cost, hashes)
if err := expectResponse(peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
t.Errorf("receipts mismatch: %v", err)
}
}
// Tests that trie merkle proofs can be retrieved
func TestGetProofsLes1(t *testing.T) { testGetReceipt(t, 1) }
func testGetProofs(t *testing.T, protocol int) {
// Assemble the test environment
pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
bc := pm.blockchain.(*core.BlockChain)
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
defer peer.close()
var proofreqs []ProofReq
var proofs [][]rlp.RawValue
accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, common.Address{}}
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
header := bc.GetHeaderByNumber(i)
root := header.Root
trie, _ := trie.NewSecure(root, db)
for _, acc := range accounts {
req := ProofReq{
BHash: header.Hash(),
Key: acc[:],
}
proofreqs = append(proofreqs, req)
proof := trie.Prove(acc[:])
proofs = append(proofs, proof)
}
}
// Send the proof request and verify the response
cost := peer.GetRequestCost(GetProofsMsg, len(proofreqs))
sendRequest(peer.app, GetProofsMsg, 42, cost, proofreqs)
if err := expectResponse(peer.app, ProofsMsg, 42, testBufLimit, proofs); err != nil {
t.Errorf("proofs mismatch: %v", err)
}
}

316
les/helper_test.go Normal file
View file

@ -0,0 +1,316 @@
// This file contains some shares testing functionality, common to multiple
// different files and modules being tested.
package les
import (
"crypto/ecdsa"
"crypto/rand"
"math/big"
"sync"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/params"
)
var (
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds = big.NewInt(1000000)
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
testContractAddr common.Address
testContractCodeDeployed = testContractCode[16:]
testContractDeployed = uint64(2)
testBufLimit = uint64(100)
)
/*
contract test {
uint256[100] data;
function Put(uint256 addr, uint256 value) {
data[addr] = value;
}
function Get(uint256 addr) constant returns (uint256 value) {
return data[addr];
}
}
*/
func testChainGen(i int, block *core.BlockGen) {
switch i {
case 0:
// In block 1, the test bank sends account #1 some ether.
tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(testBankKey)
block.AddTx(tx)
case 1:
// In block 2, the test bank sends some more ether to account #1.
// acc1Addr passes it on to account #2.
// acc1Addr creates a test contract.
tx1, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(testBankKey)
nonce := block.TxNonce(acc1Addr)
tx2, _ := types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key)
nonce++
tx3, _ := types.NewContractCreation(nonce, big.NewInt(0), big.NewInt(200000), big.NewInt(0), testContractCode).SignECDSA(acc1Key)
testContractAddr = crypto.CreateAddress(acc1Addr, nonce)
block.AddTx(tx1)
block.AddTx(tx2)
block.AddTx(tx3)
case 2:
// Block 3 is empty but was mined by account #2.
block.SetCoinbase(acc2Addr)
block.SetExtra([]byte("yeehaw"))
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), big.NewInt(100000), nil, data).SignECDSA(testBankKey)
block.AddTx(tx)
case 3:
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
b2 := block.PrevBlock(1).Header()
b2.Extra = []byte("foo")
block.AddUncle(b2)
b3 := block.PrevBlock(2).Header()
b3.Extra = []byte("foo")
block.AddUncle(b3)
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), big.NewInt(100000), nil, data).SignECDSA(testBankKey)
block.AddTx(tx)
}
}
func testRCL() RequestCostList {
cl := make(RequestCostList, len(reqList))
for i, code := range reqList {
cl[i].MsgCode = code
cl[i].BaseCost = 0
cl[i].ReqCost = 0
}
return cl
}
// newTestProtocolManager creates a new protocol manager for testing purposes,
// with the given number of blocks already known, and potential notification
// channels for different events.
func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *core.BlockGen)) (*ProtocolManager, ethdb.Database, *LesOdr, error) {
var (
evmux = new(event.TypeMux)
pow = new(core.FakePow)
db, _ = ethdb.NewMemDatabase()
genesis = core.WriteGenesisBlockForTesting(db, core.GenesisAccount{testBankAddress, testBankFunds})
chainConfig = &core.ChainConfig{HomesteadBlock: big.NewInt(0)} // homestead set to 0 because of chain maker
odr *LesOdr
chain BlockChain
)
if lightSync {
odr = NewLesOdr(db)
chain, _ = light.NewLightChain(odr, chainConfig, pow, evmux)
} else {
blockchain, _ := core.NewBlockChain(db, chainConfig, pow, evmux)
gchain, _ := core.GenerateChain(genesis, db, blocks, generator)
if _, err := blockchain.InsertChain(gchain); err != nil {
panic(err)
}
chain = blockchain
}
pm, err := NewProtocolManager(chainConfig, lightSync, NetworkId, evmux, pow, chain, nil, db, odr, nil)
if err != nil {
return nil, nil, nil, err
}
if !lightSync {
srv := &LesServer{protocolManager: pm}
pm.server = srv
srv.defParams = &flowcontrol.ServerParams{
BufLimit: testBufLimit,
MinRecharge: 1,
}
srv.fcManager = flowcontrol.NewClientManager(50, 10, 1000000000)
srv.fcCostStats = newCostStats(nil)
srv.fcCostStats.baseCost = 0
for _, entry := range srv.fcCostStats.avg {
entry.baseCost = 0
entry.reqCost = 0
}
}
pm.Start()
return pm, db, odr, nil
}
// newTestProtocolManagerMust creates a new protocol manager for testing purposes,
// with the given number of blocks already known, and potential notification
// channels for different events. In case of an error, the constructor force-
// fails the test.
func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen)) (*ProtocolManager, ethdb.Database, *LesOdr) {
pm, db, odr, err := newTestProtocolManager(lightSync, blocks, generator)
if err != nil {
t.Fatalf("Failed to create protocol manager: %v", err)
}
return pm, db, odr
}
// testTxPool is a fake, helper transaction pool for testing purposes
type testTxPool struct {
pool []*types.Transaction // Collection of all transactions
added chan<- []*types.Transaction // Notification channel for new transactions
lock sync.RWMutex // Protects the transaction pool
}
// AddTransactions appends a batch of transactions to the pool, and notifies any
// listeners if the addition channel is non nil
func (p *testTxPool) AddTransactions(txs []*types.Transaction) {
p.lock.Lock()
defer p.lock.Unlock()
p.pool = append(p.pool, txs...)
if p.added != nil {
p.added <- txs
}
}
// GetTransactions returns all the transactions known to the pool
func (p *testTxPool) GetTransactions() types.Transactions {
p.lock.RLock()
defer p.lock.RUnlock()
txs := make([]*types.Transaction, len(p.pool))
copy(txs, p.pool)
return txs
}
// newTestTransaction create a new dummy transaction.
func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction {
tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), big.NewInt(100000), big.NewInt(0), make([]byte, datasize))
tx, _ = tx.SignECDSA(from)
return tx
}
// testPeer is a simulated peer to allow testing direct network calls.
type testPeer struct {
net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
*peer
}
// newTestPeer creates a new peer registered at the given protocol manager.
func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
// Generate a random id and create the peer
var id discover.NodeID
rand.Read(id[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
// Start the peer on a new thread
errc := make(chan error, 1)
go func() {
select {
case pm.newPeerCh <- peer:
errc <- pm.handle(peer)
case <-pm.quitSync:
errc <- p2p.DiscQuitting
}
}()
tp := &testPeer{
app: app,
net: net,
peer: peer,
}
// Execute any implicitly requested handshakes and return
if shake {
td, head, genesis := pm.blockchain.Status()
tp.handshake(t, td, head, genesis)
}
return tp, errc
}
func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer, <-chan error, *peer, <-chan error) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
// Generate a random id and create the peer
var id discover.NodeID
rand.Read(id[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
peer2 := pm2.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), app)
// Start the peer on a new thread
errc := make(chan error, 1)
errc2 := make(chan error, 1)
go func() {
select {
case pm.newPeerCh <- peer:
errc <- pm.handle(peer)
case <-pm.quitSync:
errc <- p2p.DiscQuitting
}
}()
go func() {
select {
case pm2.newPeerCh <- peer2:
errc2 <- pm2.handle(peer2)
case <-pm2.quitSync:
errc2 <- p2p.DiscQuitting
}
}()
return peer, errc, peer2, errc2
}
// handshake simulates a trivial handshake that expects the same state from the
// remote side as we are simulating locally.
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) {
var expList keyValueList
expList = expList.add("protocolVersion", uint64(p.version))
expList = expList.add("networkId", uint64(NetworkId))
expList = expList.add("td", td)
expList = expList.add("bestHash", head)
expList = expList.add("genesisHash", genesis)
sendList := make(keyValueList, len(expList))
copy(sendList, expList)
expList = expList.add("serveHeaders", nil)
expList = expList.add("serveChainSince", uint64(0))
expList = expList.add("serveStateSince", uint64(0))
expList = expList.add("txRelay", nil)
expList = expList.add("flowControl/BL", testBufLimit)
expList = expList.add("flowControl/MRR", uint64(1))
expList = expList.add("flowControl/MRC", testRCL())
if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
t.Fatalf("status recv: %v", err)
}
if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
t.Fatalf("status send: %v", err)
}
}
// close terminates the local side of the peer, notifying the remote protocol
// manager of termination.
func (p *testPeer) close() {
p.app.Close()
}

111
les/metrics.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
)
var (
/* propTxnInPacketsMeter = metrics.NewMeter("eth/prop/txns/in/packets")
propTxnInTrafficMeter = metrics.NewMeter("eth/prop/txns/in/traffic")
propTxnOutPacketsMeter = metrics.NewMeter("eth/prop/txns/out/packets")
propTxnOutTrafficMeter = metrics.NewMeter("eth/prop/txns/out/traffic")
propHashInPacketsMeter = metrics.NewMeter("eth/prop/hashes/in/packets")
propHashInTrafficMeter = metrics.NewMeter("eth/prop/hashes/in/traffic")
propHashOutPacketsMeter = metrics.NewMeter("eth/prop/hashes/out/packets")
propHashOutTrafficMeter = metrics.NewMeter("eth/prop/hashes/out/traffic")
propBlockInPacketsMeter = metrics.NewMeter("eth/prop/blocks/in/packets")
propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic")
propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets")
propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic")
reqHashInPacketsMeter = metrics.NewMeter("eth/req/hashes/in/packets")
reqHashInTrafficMeter = metrics.NewMeter("eth/req/hashes/in/traffic")
reqHashOutPacketsMeter = metrics.NewMeter("eth/req/hashes/out/packets")
reqHashOutTrafficMeter = metrics.NewMeter("eth/req/hashes/out/traffic")
reqBlockInPacketsMeter = metrics.NewMeter("eth/req/blocks/in/packets")
reqBlockInTrafficMeter = metrics.NewMeter("eth/req/blocks/in/traffic")
reqBlockOutPacketsMeter = metrics.NewMeter("eth/req/blocks/out/packets")
reqBlockOutTrafficMeter = metrics.NewMeter("eth/req/blocks/out/traffic")
reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets")
reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic")
reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets")
reqHeaderOutTrafficMeter = metrics.NewMeter("eth/req/headers/out/traffic")
reqBodyInPacketsMeter = metrics.NewMeter("eth/req/bodies/in/packets")
reqBodyInTrafficMeter = metrics.NewMeter("eth/req/bodies/in/traffic")
reqBodyOutPacketsMeter = metrics.NewMeter("eth/req/bodies/out/packets")
reqBodyOutTrafficMeter = metrics.NewMeter("eth/req/bodies/out/traffic")
reqStateInPacketsMeter = metrics.NewMeter("eth/req/states/in/packets")
reqStateInTrafficMeter = metrics.NewMeter("eth/req/states/in/traffic")
reqStateOutPacketsMeter = metrics.NewMeter("eth/req/states/out/packets")
reqStateOutTrafficMeter = metrics.NewMeter("eth/req/states/out/traffic")
reqReceiptInPacketsMeter = metrics.NewMeter("eth/req/receipts/in/packets")
reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic")
reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets")
reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/
miscInPacketsMeter = metrics.NewMeter("les/misc/in/packets")
miscInTrafficMeter = metrics.NewMeter("les/misc/in/traffic")
miscOutPacketsMeter = metrics.NewMeter("les/misc/out/packets")
miscOutTrafficMeter = metrics.NewMeter("les/misc/out/traffic")
)
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
// accumulating the above defined metrics based on the data stream contents.
type meteredMsgReadWriter struct {
p2p.MsgReadWriter // Wrapped message stream to meter
version int // Protocol version to select correct meters
}
// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the
// metrics system is disabled, this fucntion returns the original object.
func newMeteredMsgWriter(rw p2p.MsgReadWriter) p2p.MsgReadWriter {
if !metrics.Enabled {
return rw
}
return &meteredMsgReadWriter{MsgReadWriter: rw}
}
// Init sets the protocol version used by the stream to know which meters to
// increment in case of overlapping message ids between protocol versions.
func (rw *meteredMsgReadWriter) Init(version int) {
rw.version = version
}
func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
// Read the message and short circuit in case of an error
msg, err := rw.MsgReadWriter.ReadMsg()
if err != nil {
return msg, err
}
// Account for the data traffic
packets, traffic := miscInPacketsMeter, miscInTrafficMeter
packets.Mark(1)
traffic.Mark(int64(msg.Size))
return msg, err
}
func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
// Account for the data traffic
packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
packets.Mark(1)
traffic.Mark(int64(msg.Size))
// Send the packet to the p2p layer
return rw.MsgReadWriter.WriteMsg(msg)
}

245
les/odr.go Normal file
View file

@ -0,0 +1,245 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"sync"
"time"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"golang.org/x/net/context"
)
var (
softRequestTimeout = time.Millisecond * 500
hardRequestTimeout = time.Second * 10
retryPeers = time.Second * 1
)
// peerDropFn is a callback type for dropping a peer detected as malicious.
type peerDropFn func(id string)
type LesOdr struct {
light.OdrBackend
db ethdb.Database
stop chan struct{}
removePeer peerDropFn
mlock, clock sync.Mutex
sentReqs map[uint64]*sentReq
peers *odrPeerSet
lastReqID uint64
}
func NewLesOdr(db ethdb.Database) *LesOdr {
return &LesOdr{
db: db,
stop: make(chan struct{}),
peers: newOdrPeerSet(),
sentReqs: make(map[uint64]*sentReq),
}
}
func (odr *LesOdr) Stop() {
close(odr.stop)
}
func (odr *LesOdr) Database() ethdb.Database {
return odr.db
}
// validatorFunc is a function that processes a message and returns true if
// it was a meaningful answer to a given request
type validatorFunc func(ethdb.Database, *Msg) bool
// sentReq is a request waiting for an answer that satisfies its valFunc
type sentReq struct {
valFunc validatorFunc
sentTo map[*peer]chan struct{}
lock sync.RWMutex // protects acces to sentTo
answered chan struct{} // closed and set to nil when any peer answers it
}
// RegisterPeer registers a new LES peer to the ODR capable peer set
func (self *LesOdr) RegisterPeer(p *peer) error {
return self.peers.register(p)
}
// UnregisterPeer removes a peer from the ODR capable peer set
func (self *LesOdr) UnregisterPeer(p *peer) {
self.peers.unregister(p)
}
const (
MsgBlockBodies = iota
MsgCode
MsgReceipts
MsgProofs
)
// Msg encodes a LES message that delivers reply data for a request
type Msg struct {
MsgType int
ReqID uint64
Obj interface{}
}
// Deliver is called by the LES protocol manager to deliver ODR reply messages to waiting requests
func (self *LesOdr) Deliver(peer *peer, msg *Msg) error {
var delivered chan struct{}
self.mlock.Lock()
req, ok := self.sentReqs[msg.ReqID]
self.mlock.Unlock()
if ok {
req.lock.Lock()
delivered, ok = req.sentTo[peer]
req.lock.Unlock()
}
if !ok {
return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID)
}
if req.valFunc(self.db, msg) {
close(delivered)
req.lock.Lock()
if req.answered != nil {
close(req.answered)
req.answered = nil
}
req.lock.Unlock()
return nil
}
return errResp(ErrInvalidResponse, "reqID = %v", msg.ReqID)
}
func (self *LesOdr) requestPeer(req *sentReq, peer *peer, delivered, timeout chan struct{}, reqWg *sync.WaitGroup) {
stime := time.Now()
defer func() {
req.lock.Lock()
delete(req.sentTo, peer)
req.lock.Unlock()
reqWg.Done()
}()
select {
case <-delivered:
servTime := uint64(time.Now().Sub(stime))
self.peers.updateTimeout(peer, false)
self.peers.updateServTime(peer, servTime)
return
case <-time.After(softRequestTimeout):
close(timeout)
if self.peers.updateTimeout(peer, true) {
self.removePeer(peer.id)
}
case <-self.stop:
return
}
select {
case <-delivered:
servTime := uint64(time.Now().Sub(stime))
self.peers.updateServTime(peer, servTime)
return
case <-time.After(hardRequestTimeout):
self.removePeer(peer.id)
case <-self.stop:
return
}
}
// networkRequest sends a request to known peers until an answer is received
// or the context is cancelled
func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) error {
answered := make(chan struct{})
req := &sentReq{
valFunc: lreq.Valid,
sentTo: make(map[*peer]chan struct{}),
answered: answered, // reply delivered by any peer
}
reqID := self.getNextReqID()
self.mlock.Lock()
self.sentReqs[reqID] = req
self.mlock.Unlock()
reqWg := new(sync.WaitGroup)
reqWg.Add(1)
defer reqWg.Done()
go func() {
reqWg.Wait()
self.mlock.Lock()
delete(self.sentReqs, reqID)
self.mlock.Unlock()
}()
exclude := make(map[*peer]struct{})
for {
if peer := self.peers.bestPeer(lreq, exclude); peer == nil {
select {
case <-ctx.Done():
return ctx.Err()
case <-req.answered:
return nil
case <-time.After(retryPeers):
}
} else {
exclude[peer] = struct{}{}
delivered := make(chan struct{})
timeout := make(chan struct{})
req.lock.Lock()
req.sentTo[peer] = delivered
req.lock.Unlock()
reqWg.Add(1)
cost := lreq.GetCost(peer)
peer.fcServer.SendRequest(reqID, cost)
go self.requestPeer(req, peer, delivered, timeout, reqWg)
lreq.Request(reqID, peer)
select {
case <-ctx.Done():
return ctx.Err()
case <-answered:
return nil
case <-timeout:
}
}
}
}
// Retrieve tries to fetch an object from the local db, then from the LES network.
// If the network retrieval was successful, it stores the object in local db.
func (self *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
lreq := LesRequest(req)
err = self.networkRequest(ctx, lreq)
if err == nil {
// retrieved from network, store in db
req.StoreResult(self.db)
} else {
glog.V(logger.Debug).Infof("networkRequest err = %v", err)
}
return
}
func (self *LesOdr) getNextReqID() uint64 {
self.clock.Lock()
defer self.clock.Unlock()
self.lastReqID++
return self.lastReqID
}

119
les/odr_peerset.go Normal file
View file

@ -0,0 +1,119 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"sync"
)
const dropTimeoutRatio = 20
type odrPeerInfo struct {
reqTimeSum, reqTimeCnt, reqCnt, timeoutCnt uint64
}
// odrPeerSet represents the collection of active peer participating in the block
// download procedure.
type odrPeerSet struct {
peers map[*peer]*odrPeerInfo
lock sync.RWMutex
}
// newPeerSet creates a new peer set top track the active download sources.
func newOdrPeerSet() *odrPeerSet {
return &odrPeerSet{
peers: make(map[*peer]*odrPeerInfo),
}
}
// Register injects a new peer into the working set, or returns an error if the
// peer is already known.
func (ps *odrPeerSet) register(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[p]; ok {
return errAlreadyRegistered
}
ps.peers[p] = &odrPeerInfo{}
return nil
}
// Unregister removes a remote peer from the active set, disabling any further
// actions to/from that particular entity.
func (ps *odrPeerSet) unregister(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[p]; !ok {
return errNotRegistered
}
delete(ps.peers, p)
return nil
}
func (ps *odrPeerSet) peerPriority(p *peer, info *odrPeerInfo, req LesOdrRequest) uint64 {
tm := p.fcServer.CanSend(req.GetCost(p))
if info.reqCnt > 0 {
tm += info.reqTimeSum / info.reqTimeCnt
}
return tm
}
func (ps *odrPeerSet) bestPeer(req LesOdrRequest, exclude map[*peer]struct{}) *peer {
var best *peer
var bpv uint64
ps.lock.Lock()
defer ps.lock.Unlock()
for p, info := range ps.peers {
if _, ok := exclude[p]; !ok {
pv := ps.peerPriority(p, info, req)
if best == nil || pv < bpv {
best = p
bpv = pv
}
}
}
return best
}
func (ps *odrPeerSet) updateTimeout(p *peer, timeout bool) (drop bool) {
ps.lock.Lock()
defer ps.lock.Unlock()
if info, ok := ps.peers[p]; ok {
info.reqCnt++
if timeout {
// check ratio before increase to allow an extra timeout
if info.timeoutCnt*dropTimeoutRatio >= info.reqCnt {
return true
}
info.timeoutCnt++
}
}
return false
}
func (ps *odrPeerSet) updateServTime(p *peer, servTime uint64) {
ps.lock.Lock()
defer ps.lock.Unlock()
if info, ok := ps.peers[p]; ok {
info.reqTimeSum += servTime
info.reqTimeCnt++
}
}

254
les/odr_requests.go Normal file
View file

@ -0,0 +1,254 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package light implements on-demand retrieval capable state and chain objects
// for the Ethereum Light Client.
package les
import (
"bytes"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
type LesOdrRequest interface {
GetCost(*peer) uint64
Request(uint64, *peer) error
Valid(ethdb.Database, *Msg) bool // if true, keeps the retrieved object
}
func LesRequest(req light.OdrRequest) LesOdrRequest {
switch r := req.(type) {
case *light.BlockRequest:
return (*BlockRequest)(r)
case *light.ReceiptsRequest:
return (*ReceiptsRequest)(r)
case *light.TrieRequest:
return (*TrieRequest)(r)
case *light.CodeRequest:
return (*CodeRequest)(r)
default:
return nil
}
}
// BlockRequest is the ODR request type for block bodies
type BlockRequest light.BlockRequest
// GetCost returns the cost of the given ODR request according to the serving
// peer's cost table (implementation of LesOdrRequest)
func (self *BlockRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetBlockBodiesMsg, 1)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *BlockRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting body of block %08x from peer %v", self.Hash[:4], peer.id)
return peer.RequestBodies(reqID, self.GetCost(peer), []common.Hash{self.Hash})
}
// Valid processes an ODR request reply message from the LES network
// returns true and stores results in memory if the message was a valid reply
// to the request (implementation of LesOdrRequest)
func (self *BlockRequest) Valid(db ethdb.Database, msg *Msg) bool {
glog.V(logger.Debug).Infof("ODR: validating body of block %08x", self.Hash[:4])
if msg.MsgType != MsgBlockBodies {
glog.V(logger.Debug).Infof("ODR: invalid message type")
return false
}
bodies := msg.Obj.([]*types.Body)
if len(bodies) != 1 {
glog.V(logger.Debug).Infof("ODR: invalid number of entries: %d", len(bodies))
return false
}
body := bodies[0]
header := core.GetHeader(db, self.Hash, self.Number)
if header == nil {
glog.V(logger.Debug).Infof("ODR: header not found for block %08x", self.Hash[:4])
return false
}
txHash := types.DeriveSha(types.Transactions(body.Transactions))
if header.TxHash != txHash {
glog.V(logger.Debug).Infof("ODR: header.TxHash %08x does not match received txHash %08x", header.TxHash[:4], txHash[:4])
return false
}
uncleHash := types.CalcUncleHash(body.Uncles)
if header.UncleHash != uncleHash {
glog.V(logger.Debug).Infof("ODR: header.UncleHash %08x does not match received uncleHash %08x", header.UncleHash[:4], uncleHash[:4])
return false
}
data, err := rlp.EncodeToBytes(body)
if err != nil {
glog.V(logger.Debug).Infof("ODR: body RLP encode error: %v", err)
return false
}
self.Rlp = data
glog.V(logger.Debug).Infof("ODR: validation successful")
return true
}
// ReceiptsRequest is the ODR request type for block receipts by block hash
type ReceiptsRequest light.ReceiptsRequest
// GetCost returns the cost of the given ODR request according to the serving
// peer's cost table (implementation of LesOdrRequest)
func (self *ReceiptsRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetReceiptsMsg, 1)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *ReceiptsRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting receipts for block %08x from peer %v", self.Hash[:4], peer.id)
return peer.RequestReceipts(reqID, self.GetCost(peer), []common.Hash{self.Hash})
}
// Valid processes an ODR request reply message from the LES network
// returns true and stores results in memory if the message was a valid reply
// to the request (implementation of LesOdrRequest)
func (self *ReceiptsRequest) Valid(db ethdb.Database, msg *Msg) bool {
glog.V(logger.Debug).Infof("ODR: validating receipts for block %08x", self.Hash[:4])
if msg.MsgType != MsgReceipts {
glog.V(logger.Debug).Infof("ODR: invalid message type")
return false
}
receipts := msg.Obj.([]types.Receipts)
if len(receipts) != 1 {
glog.V(logger.Debug).Infof("ODR: invalid number of entries: %d", len(receipts))
return false
}
hash := types.DeriveSha(receipts[0])
header := core.GetHeader(db, self.Hash, self.Number)
if header == nil {
glog.V(logger.Debug).Infof("ODR: header not found for block %08x", self.Hash[:4])
return false
}
if !bytes.Equal(header.ReceiptHash[:], hash[:]) {
glog.V(logger.Debug).Infof("ODR: header receipts hash %08x does not match calculated RLP hash %08x", header.ReceiptHash[:4], hash[:4])
return false
}
self.Receipts = receipts[0]
glog.V(logger.Debug).Infof("ODR: validation successful")
return true
}
type ProofReq struct {
BHash common.Hash
AccKey, Key []byte
FromLevel uint
}
// ODR request type for state/storage trie entries, see LesOdrRequest interface
type TrieRequest light.TrieRequest
// GetCost returns the cost of the given ODR request according to the serving
// peer's cost table (implementation of LesOdrRequest)
func (self *TrieRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetProofsMsg, 1)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *TrieRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting trie root %08x key %08x from peer %v", self.Id.Root[:4], self.Key[:4], peer.id)
req := &ProofReq{
BHash: self.Id.BlockHash,
AccKey: self.Id.AccKey,
Key: self.Key,
}
return peer.RequestProofs(reqID, self.GetCost(peer), []*ProofReq{req})
}
// Valid processes an ODR request reply message from the LES network
// returns true and stores results in memory if the message was a valid reply
// to the request (implementation of LesOdrRequest)
func (self *TrieRequest) Valid(db ethdb.Database, msg *Msg) bool {
glog.V(logger.Debug).Infof("ODR: validating trie root %08x key %08x", self.Id.Root[:4], self.Key[:4])
if msg.MsgType != MsgProofs {
glog.V(logger.Debug).Infof("ODR: invalid message type")
return false
}
proofs := msg.Obj.([][]rlp.RawValue)
if len(proofs) != 1 {
glog.V(logger.Debug).Infof("ODR: invalid number of entries: %d", len(proofs))
return false
}
_, err := trie.VerifyProof(self.Id.Root, self.Key, proofs[0])
if err != nil {
glog.V(logger.Debug).Infof("ODR: merkle proof verification error: %v", err)
return false
}
self.Proof = proofs[0]
glog.V(logger.Debug).Infof("ODR: validation successful")
return true
}
type CodeReq struct {
BHash common.Hash
AccKey []byte
}
// ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface
type CodeRequest light.CodeRequest
// GetCost returns the cost of the given ODR request according to the serving
// peer's cost table (implementation of LesOdrRequest)
func (self *CodeRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetCodeMsg, 1)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *CodeRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting node data for hash %08x from peer %v", self.Hash[:4], peer.id)
req := &CodeReq{
BHash: self.Id.BlockHash,
AccKey: self.Id.AccKey,
}
return peer.RequestCode(reqID, self.GetCost(peer), []*CodeReq{req})
}
// Valid processes an ODR request reply message from the LES network
// returns true and stores results in memory if the message was a valid reply
// to the request (implementation of LesOdrRequest)
func (self *CodeRequest) Valid(db ethdb.Database, msg *Msg) bool {
glog.V(logger.Debug).Infof("ODR: validating node data for hash %08x", self.Hash[:4])
if msg.MsgType != MsgCode {
glog.V(logger.Debug).Infof("ODR: invalid message type")
return false
}
reply := msg.Obj.([][]byte)
if len(reply) != 1 {
glog.V(logger.Debug).Infof("ODR: invalid number of entries: %d", len(reply))
return false
}
data := reply[0]
hash := crypto.Sha3Hash(data)
if !bytes.Equal(self.Hash[:], hash[:]) {
glog.V(logger.Debug).Infof("ODR: requested hash %08x does not match received data hash %08x", self.Hash[:4], hash[:4])
return false
}
self.Data = data
glog.V(logger.Debug).Infof("ODR: validation successful")
return true
}

223
les/odr_test.go Normal file
View file

@ -0,0 +1,223 @@
package les
import (
"bytes"
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/net/context"
)
type odrTestFn func(ctx context.Context, db ethdb.Database, config *core.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte
func TestOdrGetBlockLes1(t *testing.T) { testOdr(t, 1, 1, odrGetBlock) }
func odrGetBlock(ctx context.Context, db ethdb.Database, config *core.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
var block *types.Block
if bc != nil {
block = bc.GetBlockByHash(bhash)
} else {
block, _ = lc.GetBlockByHash(ctx, bhash)
}
if block == nil {
return nil
}
rlp, _ := rlp.EncodeToBytes(block)
return rlp
}
func TestOdrGetReceiptsLes1(t *testing.T) { testOdr(t, 1, 1, odrGetReceipts) }
func odrGetReceipts(ctx context.Context, db ethdb.Database, config *core.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
var receipts types.Receipts
if bc != nil {
receipts = core.GetBlockReceipts(db, bhash, core.GetBlockNumber(db, bhash))
} else {
receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, core.GetBlockNumber(db, bhash))
}
if receipts == nil {
return nil
}
rlp, _ := rlp.EncodeToBytes(receipts)
return rlp
}
func TestOdrAccountsLes1(t *testing.T) { testOdr(t, 1, 1, odrAccounts) }
func odrAccounts(ctx context.Context, db ethdb.Database, config *core.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
trie.ClearGlobalCache()
var res []byte
for _, addr := range acc {
if bc != nil {
header := bc.GetHeaderByHash(bhash)
st, err := state.New(header.Root, db)
if err == nil {
bal := st.GetBalance(addr)
rlp, _ := rlp.EncodeToBytes(bal)
res = append(res, rlp...)
}
} else {
header := lc.GetHeaderByHash(bhash)
st := light.NewLightState(light.StateTrieID(header), lc.Odr())
bal, err := st.GetBalance(ctx, addr)
if err == nil {
rlp, _ := rlp.EncodeToBytes(bal)
res = append(res, rlp...)
}
}
}
return res
}
func TestOdrContractCallLes1(t *testing.T) { testOdr(t, 1, 2, odrContractCall) }
// fullcallmsg is the message type used for call transations.
type fullcallmsg struct {
from *state.StateObject
to *common.Address
gas, gasPrice *big.Int
value *big.Int
data []byte
}
// accessor boilerplate to implement core.Message
func (m fullcallmsg) From() (common.Address, error) { return m.from.Address(), nil }
func (m fullcallmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil }
func (m fullcallmsg) Nonce() uint64 { return m.from.Nonce() }
func (m fullcallmsg) To() *common.Address { return m.to }
func (m fullcallmsg) GasPrice() *big.Int { return m.gasPrice }
func (m fullcallmsg) Gas() *big.Int { return m.gas }
func (m fullcallmsg) Value() *big.Int { return m.value }
func (m fullcallmsg) Data() []byte { return m.data }
// callmsg is the message type used for call transations.
type lightcallmsg struct {
from *light.StateObject
to *common.Address
gas, gasPrice *big.Int
value *big.Int
data []byte
}
// accessor boilerplate to implement core.Message
func (m lightcallmsg) From() (common.Address, error) { return m.from.Address(), nil }
func (m lightcallmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil }
func (m lightcallmsg) Nonce() uint64 { return m.from.Nonce() }
func (m lightcallmsg) To() *common.Address { return m.to }
func (m lightcallmsg) GasPrice() *big.Int { return m.gasPrice }
func (m lightcallmsg) Gas() *big.Int { return m.gas }
func (m lightcallmsg) Value() *big.Int { return m.value }
func (m lightcallmsg) Data() []byte { return m.data }
func odrContractCall(ctx context.Context, db ethdb.Database, config *core.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
var res []byte
for i := 0; i < 3; i++ {
data[35] = byte(i)
if bc != nil {
header := bc.GetHeaderByHash(bhash)
statedb, err := state.New(header.Root, db)
if err == nil {
from := statedb.GetOrNewStateObject(testBankAddress)
from.SetBalance(common.MaxBig)
msg := fullcallmsg{
from: from,
gas: big.NewInt(100000),
gasPrice: big.NewInt(0),
value: big.NewInt(0),
data: data,
to: &testContractAddr,
}
vmenv := core.NewEnv(statedb, config, bc, msg, header, config.VmConfig)
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...)
}
} else {
header := lc.GetHeaderByHash(bhash)
state := light.NewLightState(light.StateTrieID(header), lc.Odr())
from, err := state.GetOrNewStateObject(ctx, testBankAddress)
if err == nil {
from.SetBalance(common.MaxBig)
msg := lightcallmsg{
from: from,
gas: big.NewInt(100000),
gasPrice: big.NewInt(0),
value: big.NewInt(0),
data: data,
to: &testContractAddr,
}
vmenv := light.NewEnv(ctx, state, config, lc, msg, header, config.VmConfig)
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
if vmenv.Error() == nil {
res = append(res, ret...)
}
}
}
}
return res
}
func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
// Assemble the test environment
pm, db, odr := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
t.Fatalf("peer 1 handshake error: %v", err)
case err := <-err2:
t.Fatalf("peer 1 handshake error: %v", err)
}
lpm.synchronise(lpeer, true)
test := func(expFail uint64) {
for i := uint64(0); i <= pm.blockchain.CurrentHeader().GetNumberU64(); i++ {
bhash := core.GetCanonicalHash(db, i)
b1 := fn(light.NoOdr, db, pm.chainConfig, pm.blockchain.(*core.BlockChain), nil, bhash)
ctx, _ := context.WithTimeout(context.Background(), 200*time.Millisecond)
b2 := fn(ctx, ldb, lpm.chainConfig, nil, lpm.blockchain.(*light.LightChain), bhash)
eq := bytes.Equal(b1, b2)
exp := i < expFail
if exp && !eq {
t.Errorf("odr mismatch")
}
if !exp && eq {
t.Errorf("unexpected odr match")
}
}
}
// temporarily remove peer to test odr fails
odr.UnregisterPeer(lpeer)
// expect retrievals to fail (except genesis block) without a les peer
test(expFail)
odr.RegisterPeer(lpeer)
// expect all retrievals to pass
test(5)
odr.UnregisterPeer(lpeer)
// still expect all retrievals to pass, now data should be cached locally
test(5)
}

482
les/peer.go Normal file
View file

@ -0,0 +1,482 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"errors"
"fmt"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"gopkg.in/fatih/set.v0"
)
var (
errClosed = errors.New("peer set is closed")
errAlreadyRegistered = errors.New("peer is already registered")
errNotRegistered = errors.New("peer is not registered")
)
const (
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
)
type peer struct {
*p2p.Peer
rw p2p.MsgReadWriter
version int // Protocol version negotiated
network int // Network ID being on
id string
head common.Hash
td *big.Int
lock sync.RWMutex
knownBlocks *set.Set // Set of block hashes known to be known by this peer
fcClient *flowcontrol.ClientNode // nil if the peer is server only
fcServer *flowcontrol.ServerNode // nil if the peer is client only
fcCosts requestCostTable
}
func newPeer(version, network int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID()
return &peer{
Peer: p,
rw: rw,
version: version,
network: network,
id: fmt.Sprintf("%x", id[:8]),
knownBlocks: set.New(),
}
}
// Info gathers and returns a collection of metadata known about a peer.
func (p *peer) Info() *eth.PeerInfo {
return &eth.PeerInfo{
Version: p.version,
Difficulty: p.Td(),
Head: fmt.Sprintf("%x", p.Head()),
}
}
// Head retrieves a copy of the current head (most recent) hash of the peer.
func (p *peer) Head() (hash common.Hash) {
p.lock.RLock()
defer p.lock.RUnlock()
copy(hash[:], p.head[:])
return hash
}
// Td retrieves the current total difficulty of a peer.
func (p *peer) Td() *big.Int {
p.lock.RLock()
defer p.lock.RUnlock()
return new(big.Int).Set(p.td)
}
func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{}) error {
type req struct {
ReqID uint64
Data interface{}
}
return p2p.Send(w, msgcode, req{reqID, data})
}
func sendResponse(w p2p.MsgWriter, msgcode, reqID, bv uint64, data interface{}) error {
type resp struct {
ReqID, BV uint64
Data interface{}
}
return p2p.Send(w, msgcode, resp{reqID, bv, data})
}
func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
return p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
}
// SendBlockHeaders sends a batch of block headers to the remote peer.
func (p *peer) SendBlockHeaders(reqID, bv uint64, headers []*types.Header) error {
return sendResponse(p.rw, BlockHeadersMsg, reqID, bv, headers)
}
// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
// an already RLP encoded format.
func (p *peer) SendBlockBodiesRLP(reqID, bv uint64, bodies []rlp.RawValue) error {
return sendResponse(p.rw, BlockBodiesMsg, reqID, bv, bodies)
}
// SendCodeRLP sends a batch of arbitrary internal data, corresponding to the
// hashes requested.
func (p *peer) SendCode(reqID, bv uint64, data [][]byte) error {
return sendResponse(p.rw, CodeMsg, reqID, bv, data)
}
// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
// ones requested from an already RLP encoded format.
func (p *peer) SendReceiptsRLP(reqID, bv uint64, receipts []rlp.RawValue) error {
return sendResponse(p.rw, ReceiptsMsg, reqID, bv, receipts)
}
// SendProofs sends a batch of merkle proofs, corresponding to the ones requested.
func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error {
return sendResponse(p.rw, ProofsMsg, reqID, bv, proofs)
}
// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
// specified header query, based on the hash of an origin block.
func (p *peer) RequestHeadersByHash(reqID, cost uint64, origin common.Hash, amount int, skip int, reverse bool) error {
glog.V(logger.Debug).Infof("%v fetching %d headers from %x, skipping %d (reverse = %v)", p, amount, origin[:4], skip, reverse)
return sendRequest(p.rw, GetBlockHeadersMsg, reqID, cost, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
}
// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the
// specified header query, based on the number of an origin block.
func (p *peer) RequestHeadersByNumber(reqID, cost, origin uint64, amount int, skip int, reverse bool) error {
glog.V(logger.Debug).Infof("%v fetching %d headers from #%d, skipping %d (reverse = %v)", p, amount, origin, skip, reverse)
return sendRequest(p.rw, GetBlockHeadersMsg, reqID, cost, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
}
// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
// specified.
func (p *peer) RequestBodies(reqID, cost uint64, hashes []common.Hash) error {
glog.V(logger.Debug).Infof("%v fetching %d block bodies", p, len(hashes))
return sendRequest(p.rw, GetBlockBodiesMsg, reqID, cost, hashes)
}
// RequestCode fetches a batch of arbitrary data from a node's known state
// data, corresponding to the specified hashes.
func (p *peer) RequestCode(reqID, cost uint64, reqs []*CodeReq) error {
glog.V(logger.Debug).Infof("%v fetching %v state data", p, len(reqs))
return sendRequest(p.rw, GetCodeMsg, reqID, cost, reqs)
}
// RequestReceipts fetches a batch of transaction receipts from a remote node.
func (p *peer) RequestReceipts(reqID, cost uint64, hashes []common.Hash) error {
glog.V(logger.Debug).Infof("%v fetching %v receipts", p, len(hashes))
return sendRequest(p.rw, GetReceiptsMsg, reqID, cost, hashes)
}
// RequestProofs fetches a batch of merkle proofs from a remote node.
func (p *peer) RequestProofs(reqID, cost uint64, reqs []*ProofReq) error {
glog.V(logger.Debug).Infof("%v fetching %v proofs", p, len(reqs))
return sendRequest(p.rw, GetProofsMsg, reqID, cost, reqs)
}
func (p *peer) SendTxs(cost uint64, txs types.Transactions) error {
glog.V(logger.Debug).Infof("%v relaying %v txs", p, len(txs))
p.fcServer.SendRequest(0, cost)
return p2p.Send(p.rw, SendTxMsg, txs)
}
type keyValueEntry struct{
Key string
Value rlp.RawValue
}
type keyValueList []keyValueEntry
type keyValueMap map[string]rlp.RawValue
func (l keyValueList) add(key string, val interface{}) keyValueList {
var entry keyValueEntry
entry.Key = key
if val == nil {
val = uint64(0)
}
enc, err := rlp.EncodeToBytes(val)
if err == nil {
entry.Value = enc
}
return append(l, entry)
}
func (l keyValueList) decode() keyValueMap {
m := make(keyValueMap)
for _, entry := range l {
m[entry.Key] = entry.Value
}
return m
}
func (m keyValueMap) get(key string, val interface{}) error {
enc, ok := m[key]
if !ok {
return errResp(ErrHandshakeMissingKey, "%s", key)
}
if val == nil {
return nil
}
return rlp.DecodeBytes(enc, val)
}
func (p *peer) sendReceiveHandshake(sendList keyValueList) (keyValueList, error) {
// Send out own handshake in a new thread
errc := make(chan error, 1)
go func() {
errc <- p2p.Send(p.rw, StatusMsg, sendList)
}()
// In the mean time retrieve the remote status message
msg, err := p.rw.ReadMsg()
if err != nil {
return nil, err
}
if msg.Code != StatusMsg {
return nil, errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
}
if msg.Size > ProtocolMaxMsgSize {
return nil, errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
}
// Decode the handshake
var recvList keyValueList
if err := msg.Decode(&recvList); err != nil {
return nil, errResp(ErrDecode, "msg %v: %v", msg, err)
}
if err := <-errc; err != nil {
return nil, err
}
return recvList, nil
}
// Handshake executes the les protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks.
func (p *peer) Handshake(td *big.Int, head common.Hash, genesis common.Hash, server *LesServer) error {
p.lock.Lock()
defer p.lock.Unlock()
var send keyValueList
send = send.add("protocolVersion", uint64(p.version))
send = send.add("networkId", uint64(p.network))
send = send.add("td", td)
send = send.add("bestHash", head)
send = send.add("genesisHash", genesis)
if server != nil {
send = send.add("serveHeaders", nil)
send = send.add("serveChainSince", uint64(0))
send = send.add("serveStateSince", uint64(0))
send = send.add("txRelay", nil)
send = send.add("flowControl/BL", server.defParams.BufLimit)
send = send.add("flowControl/MRR", server.defParams.MinRecharge)
list := server.fcCostStats.getCurrentList()
send = send.add("flowControl/MRC", list)
p.fcCosts = list.decode()
}
recvList, err := p.sendReceiveHandshake(send)
if err != nil {
return err
}
recv := recvList.decode()
var rGenesis, rBest common.Hash
var rVersion, rNetwork uint64
var rTd *big.Int
if err := recv.get("protocolVersion", &rVersion); err != nil {
return err
}
if err := recv.get("networkId", &rNetwork); err != nil {
return err
}
if err := recv.get("td", &rTd); err != nil {
return err
}
if err := recv.get("bestHash", &rBest); err != nil {
return err
}
if err := recv.get("genesisHash", &rGenesis); err != nil {
return err
}
if rGenesis != genesis {
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis, genesis)
}
if int(rNetwork) != p.network {
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network)
}
if int(rVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
}
if server != nil {
if recv.get("serveStateSince", nil) == nil {
return errResp(ErrUselessPeer, "wanted client, got server")
}
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
} else {
if recv.get("serveChainSince", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot serve chain")
}
if recv.get("serveStateSince", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot serve state")
}
if recv.get("txRelay", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot relay transactions")
}
params := &flowcontrol.ServerParams{}
if err := recv.get("flowControl/BL", &params.BufLimit); err != nil {
return err
}
if err := recv.get("flowControl/MRR", &params.MinRecharge); err != nil {
return err
}
var MRC RequestCostList
if err := recv.get("flowControl/MRC", &MRC); err != nil {
return err
}
p.fcServer = flowcontrol.NewServerNode(params)
p.fcCosts = MRC.decode()
}
// Configure the remote peer, and sanity check out handshake too
p.td, p.head = rTd, rBest
return nil
}
// String implements fmt.Stringer.
func (p *peer) String() string {
return fmt.Sprintf("Peer %s [%s]", p.id,
fmt.Sprintf("les/%d", p.version),
)
}
// peerSet represents the collection of active peers currently participating in
// the Light Ethereum sub-protocol.
type peerSet struct {
peers map[string]*peer
lock sync.RWMutex
closed bool
}
// newPeerSet creates a new peer set to track the active participants.
func newPeerSet() *peerSet {
return &peerSet{
peers: make(map[string]*peer),
}
}
// Register injects a new peer into the working set, or returns an error if the
// peer is already known.
func (ps *peerSet) Register(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if ps.closed {
return errClosed
}
if _, ok := ps.peers[p.id]; ok {
return errAlreadyRegistered
}
ps.peers[p.id] = p
return nil
}
// Unregister removes a remote peer from the active set, disabling any further
// actions to/from that particular entity.
func (ps *peerSet) Unregister(id string) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[id]; !ok {
return errNotRegistered
}
delete(ps.peers, id)
return nil
}
// AllPeerIDs returns a list of all registered peer IDs
func (ps *peerSet) AllPeerIDs() []string {
ps.lock.RLock()
defer ps.lock.RUnlock()
res := make([]string, len(ps.peers))
idx := 0
for id, _ := range ps.peers {
res[idx] = id
idx++
}
return res
}
// Peer retrieves the registered peer with the given id.
func (ps *peerSet) Peer(id string) *peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
return ps.peers[id]
}
// Len returns if the current number of peers in the set.
func (ps *peerSet) Len() int {
ps.lock.RLock()
defer ps.lock.RUnlock()
return len(ps.peers)
}
// BestPeer retrieves the known peer with the currently highest total difficulty.
func (ps *peerSet) BestPeer() *peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
var (
bestPeer *peer
bestTd *big.Int
)
for _, p := range ps.peers {
if td := p.Td(); bestPeer == nil || td.Cmp(bestTd) > 0 {
bestPeer, bestTd = p, td
}
}
return bestPeer
}
// AllPeers returns all peers in a list
func (ps *peerSet) AllPeers() []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, len(ps.peers))
i := 0
for _, peer := range ps.peers {
list[i] = peer
i++
}
return list
}
// Close disconnects all peers.
// No new peers can be registered after Close has returned.
func (ps *peerSet) Close() {
ps.lock.Lock()
defer ps.lock.Unlock()
for _, p := range ps.peers {
p.Disconnect(p2p.DiscQuitting)
}
ps.closed = true
}

196
les/protocol.go Normal file
View file

@ -0,0 +1,196 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"fmt"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
// Constants to match up protocol versions and messages
const (
lpv1 = 1
)
// Supported versions of the les protocol (first is primary).
var ProtocolVersions = []uint{lpv1}
// Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{15}
const (
NetworkId = 1
ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
)
// les protocol message codes
const (
// Protocol messages belonging to LPV1
StatusMsg = 0x00
NewBlockHashesMsg = 0x01
GetBlockHeadersMsg = 0x02
BlockHeadersMsg = 0x03
GetBlockBodiesMsg = 0x04
BlockBodiesMsg = 0x05
GetReceiptsMsg = 0x06
ReceiptsMsg = 0x07
GetProofsMsg = 0x08
ProofsMsg = 0x09
GetCodeMsg = 0x0a
CodeMsg = 0x0b
SendTxMsg = 0x0c
GetTxHashesMsg = 0x0d
TxHashesMsg = 0x0e
)
type errCode int
const (
ErrMsgTooLarge = iota
ErrDecode
ErrInvalidMsgCode
ErrProtocolVersionMismatch
ErrNetworkIdMismatch
ErrGenesisBlockMismatch
ErrNoStatusMsg
ErrExtraStatusMsg
ErrSuspendedPeer
ErrUselessPeer
ErrRequestRejected
ErrUnexpectedResponse
ErrInvalidResponse
ErrTooManyTimeouts
ErrHandshakeMissingKey
)
func (e errCode) String() string {
return errorToString[int(e)]
}
// XXX change once legacy code is out
var errorToString = map[int]string{
ErrMsgTooLarge: "Message too long",
ErrDecode: "Invalid message",
ErrInvalidMsgCode: "Invalid message code",
ErrProtocolVersionMismatch: "Protocol version mismatch",
ErrNetworkIdMismatch: "NetworkId mismatch",
ErrGenesisBlockMismatch: "Genesis block mismatch",
ErrNoStatusMsg: "No status message",
ErrExtraStatusMsg: "Extra status message",
ErrSuspendedPeer: "Suspended peer",
ErrRequestRejected: "Request rejected",
ErrUnexpectedResponse: "Unexpected response",
ErrInvalidResponse: "Invalid response",
ErrTooManyTimeouts: "Too many request timeouts",
ErrHandshakeMissingKey: "Key missing from handshake message",
}
type chainManager interface {
GetBlockHashesFromHash(hash common.Hash, amount uint64) (hashes []common.Hash)
GetBlock(hash common.Hash) (block *types.Block)
Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash)
}
// statusData is the network packet for the status message.
type statusData struct {
ProtocolVersion uint32
NetworkId uint32
TD *big.Int
CurrentBlock common.Hash
GenesisBlock common.Hash
History uint64
BL, MRR uint64
MRC RequestCostList
}
// newBlockHashesData is the network packet for the block announcements.
type newBlockHashesData []struct {
Hash common.Hash // Hash of one particular block being announced
Number uint64 // Number of one particular block being announced
}
// getBlockHashesData is the network packet for the hash based hash retrieval.
type getBlockHashesData struct {
Hash common.Hash
Amount uint64
}
// getBlockHeadersData represents a block header query.
type getBlockHeadersData struct {
Origin hashOrNumber // Block from which to retrieve headers
Amount uint64 // Maximum number of headers to retrieve
Skip uint64 // Blocks to skip between consecutive headers
Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
}
// hashOrNumber is a combined field for specifying an origin block.
type hashOrNumber struct {
Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
Number uint64 // Block hash from which to retrieve headers (excludes Hash)
}
// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
// two contained union fields.
func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
if hn.Hash == (common.Hash{}) {
return rlp.Encode(w, hn.Number)
}
if hn.Number != 0 {
return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
}
return rlp.Encode(w, hn.Hash)
}
// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
// into either a block hash or a block number.
func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind()
origin, err := s.Raw()
if err == nil {
switch {
case size == 32:
err = rlp.DecodeBytes(origin, &hn.Hash)
case size <= 8:
err = rlp.DecodeBytes(origin, &hn.Number)
default:
err = fmt.Errorf("invalid input size %d for origin", size)
}
}
return err
}
// newBlockData is the network packet for the block propagation message.
type newBlockData struct {
Block *types.Block
TD *big.Int
}
// blockBodiesData is the network packet for block content distribution.
type blockBodiesData []*types.Body
// CodeData is the network response packet for a node data retrieval.
type CodeData []struct {
Value []byte
}
type proofsData [][]rlp.RawValue

94
les/request_test.go Normal file
View file

@ -0,0 +1,94 @@
package les
import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"golang.org/x/net/context"
)
var testBankSecureTrieKey = secAddr(testBankAddress)
func secAddr(addr common.Address) []byte {
return crypto.Keccak256(addr[:])
}
type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest
func TestBlockAccessLes1(t *testing.T) { testAccess(t, 1, tfBlockAccess) }
func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.BlockRequest{Hash: bhash, Number: number}
}
func TestReceiptsAccessLes1(t *testing.T) { testAccess(t, 1, tfReceiptsAccess) }
func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.ReceiptsRequest{Hash: bhash, Number: number}
}
func TestTrieEntryAccessLes1(t *testing.T) { testAccess(t, 1, tfTrieEntryAccess) }
func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.TrieRequest{Id: light.StateTrieID(core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))), Key: testBankSecureTrieKey}
}
func TestCodeAccessLes1(t *testing.T) { testAccess(t, 1, tfCodeAccess) }
func tfCodeAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
header := core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))
if header.GetNumberU64() < testContractDeployed {
return nil
}
sti := light.StateTrieID(header)
ci := light.StorageTrieID(sti, testContractAddr, common.Hash{})
return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)}
}
func testAccess(t *testing.T, protocol int, fn accessTestFn) {
// Assemble the test environment
pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
t.Fatalf("peer 1 handshake error: %v", err)
case err := <-err2:
t.Fatalf("peer 1 handshake error: %v", err)
}
lpm.synchronise(lpeer, true)
test := func(expFail uint64) {
for i := uint64(0); i <= pm.blockchain.CurrentHeader().GetNumberU64(); i++ {
bhash := core.GetCanonicalHash(db, i)
if req := fn(ldb, bhash, i); req != nil {
ctx, _ := context.WithTimeout(context.Background(), 200*time.Millisecond)
err := odr.Retrieve(ctx, req)
got := err == nil
exp := i < expFail
if exp && !got {
t.Errorf("object retrieval failed")
}
if !exp && got {
t.Errorf("unexpected object retrieval success")
}
}
}
}
// temporarily remove peer to test odr fails
odr.UnregisterPeer(lpeer)
// expect retrievals to fail (except genesis block) without a les peer
test(0)
odr.RegisterPeer(lpeer)
// expect all retrievals to pass
test(5)
odr.UnregisterPeer(lpeer)
}

175
les/server.go Normal file
View file

@ -0,0 +1,175 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"sync"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
)
type LesServer struct {
protocolManager *ProtocolManager
fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostStats *requestCostStats
defParams *flowcontrol.ServerParams
}
func NewLesServer(eth *eth.FullNodeService, config *eth.Config) (*LesServer, error) {
pm, err := NewProtocolManager(config.ChainConfig, false, config.NetworkId, eth.EventMux(), eth.Pow(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil)
if err != nil {
return nil, err
}
srv := &LesServer{protocolManager: pm}
pm.server = srv
srv.defParams = &flowcontrol.ServerParams{
BufLimit: 300000000,
MinRecharge: 50000,
}
srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000)
srv.fcCostStats = newCostStats(eth.ChainDb())
return srv, nil
}
func (s *LesServer) Protocols() []p2p.Protocol {
return s.protocolManager.SubProtocols
}
func (s *LesServer) Start() {
s.protocolManager.Start()
}
func (s *LesServer) Stop() {
s.fcCostStats.store()
s.fcManager.Stop()
go func() {
<-s.protocolManager.noMorePeers
}()
s.protocolManager.Stop()
}
type requestCosts struct {
baseCost, reqCost uint64
}
type requestCostTable map[uint64]*requestCosts
type RequestCostList []struct {
MsgCode, BaseCost, ReqCost uint64
}
func (list RequestCostList) decode() requestCostTable {
table := make(requestCostTable)
for _, e := range list {
table[e.MsgCode] = &requestCosts{
baseCost: e.BaseCost,
reqCost: e.ReqCost,
}
}
return table
}
func (table requestCostTable) encode() RequestCostList {
list := make(RequestCostList, len(table))
for idx, code := range reqList {
list[idx].MsgCode = code
list[idx].BaseCost = table[code].baseCost
list[idx].ReqCost = table[code].reqCost
}
return list
}
type requestCostStats struct {
lock sync.RWMutex
db ethdb.Database
avg requestCostTable
baseCost uint64
}
var rcStatsKey = []byte("requestCostStats")
func newCostStats(db ethdb.Database) *requestCostStats {
table := make(requestCostTable)
for _, code := range reqList {
table[code] = &requestCosts{0, 100000}
}
/* if db != nil {
var cl RequestCostList
data, err := db.Get(rcStatsKey)
if err == nil {
err = rlp.DecodeBytes(data, &cl)
}
if err == nil {
t := cl.decode()
for code, entry := range t {
table[code] = entry
}
}
}*/
return &requestCostStats{
db: db,
avg: table,
baseCost: 100000,
}
}
func (s *requestCostStats) store() {
s.lock.Lock()
defer s.lock.Unlock()
list := s.avg.encode()
if data, err := rlp.EncodeToBytes(list); err == nil {
s.db.Put(rcStatsKey, data)
}
}
func (s *requestCostStats) getCurrentList() RequestCostList {
s.lock.Lock()
defer s.lock.Unlock()
list := make(RequestCostList, len(s.avg))
for idx, code := range reqList {
list[idx].MsgCode = code
list[idx].BaseCost = s.baseCost
list[idx].ReqCost = s.avg[code].reqCost * 2
}
return list
}
func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) {
s.lock.Lock()
defer s.lock.Unlock()
c, ok := s.avg[msgCode]
if !ok || reqCnt == 0 {
return
}
cost = cost / reqCnt
if cost > c.reqCost {
c.reqCost += (cost - c.reqCost) / 10
} else {
c.reqCost -= (c.reqCost - cost) / 100
}
}

78
les/sync.go Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"time"
"github.com/ethereum/go-ethereum/eth/downloader"
)
const (
forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available
minDesiredPeerCount = 5 // Amount of peers desired to start syncing
)
// syncer is responsible for periodically synchronising with the network, both
// downloading hashes and blocks as well as handling the announcement handler.
func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms
//pm.fetcher.Start()
//defer pm.fetcher.Stop()
defer pm.downloader.Terminate()
// Wait for different events to fire synchronisation operations
forceSync := time.Tick(forceSyncCycle)
for {
select {
case <-pm.newPeerCh:
// Make sure we have peers to select from, then sync
if pm.peers.Len() < minDesiredPeerCount {
break
}
go pm.synchronise(pm.peers.BestPeer(), false)
case <-forceSync:
// Force a sync even if not enough peers are present
go pm.synchronise(pm.peers.BestPeer(), false)
case <-pm.noMorePeers:
return
}
}
}
// synchronise tries to sync up our local block chain with a remote peer.
func (pm *ProtocolManager) synchronise(peer *peer, exit bool) {
// Short circuit if no peers are available
if peer == nil {
return
}
// Make sure the peer's TD is higher than our own.
td := pm.blockchain.GetTdByHash(pm.blockchain.LastBlockHash())
if peer.Td().Cmp(td) > 0 {
for {
if pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync) != nil {
return
}
if exit {
return
}
time.Sleep(time.Second * 5)
}
}
}

156
les/txrelay.go Normal file
View file

@ -0,0 +1,156 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type ltrInfo struct {
tx *types.Transaction
sentTo map[*peer]struct{}
}
type LesTxRelay struct {
txSent map[common.Hash]*ltrInfo
txPending map[common.Hash]struct{}
ps *peerSet
peerList []*peer
peerStartPos int
lock sync.RWMutex
}
func NewLesTxRelay() *LesTxRelay {
return &LesTxRelay{
txSent: make(map[common.Hash]*ltrInfo),
txPending: make(map[common.Hash]struct{}),
ps: newPeerSet(),
}
}
func (self *LesTxRelay) addPeer(p *peer) {
self.lock.Lock()
defer self.lock.Unlock()
self.ps.Register(p)
self.peerList = self.ps.AllPeers()
}
func (self *LesTxRelay) removePeer(id string) {
self.lock.Lock()
defer self.lock.Unlock()
self.ps.Unregister(id)
self.peerList = self.ps.AllPeers()
}
// send sends a list of transactions to at most a given number of peers at
// once, never resending any particular transaction to the same peer twice
func (self *LesTxRelay) send(txs types.Transactions, count int) {
sendTo := make(map[*peer]types.Transactions)
self.peerStartPos++ // rotate the starting position of the peer list
if self.peerStartPos >= len(self.peerList) {
self.peerStartPos = 0
}
for _, tx := range txs {
hash := tx.Hash()
ltr, ok := self.txSent[hash]
if !ok {
ltr = &ltrInfo{
tx: tx,
sentTo: make(map[*peer]struct{}),
}
self.txSent[hash] = ltr
self.txPending[hash] = struct{}{}
}
if len(self.peerList) > 0 {
cnt := count
pos := self.peerStartPos
for {
peer := self.peerList[pos]
if _, ok := ltr.sentTo[peer]; !ok {
sendTo[peer] = append(sendTo[peer], tx)
ltr.sentTo[peer] = struct{}{}
cnt--
}
if cnt == 0 {
break // sent it to the desired number of peers
}
pos++
if pos == len(self.peerList) {
pos = 0
}
if pos == self.peerStartPos {
break // tried all available peers
}
}
}
}
for peer, list := range sendTo {
cost := peer.GetRequestCost(SendTxMsg, len(list))
go func() {
peer.fcServer.SendRequest(0, cost)
peer.SendTxs(cost, list)
}()
}
}
func (self *LesTxRelay) Send(txs types.Transactions) {
self.lock.Lock()
defer self.lock.Unlock()
self.send(txs, 3)
}
func (self *LesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
self.lock.Lock()
defer self.lock.Unlock()
for _, hash := range mined {
delete(self.txPending, hash)
}
for _, hash := range rollback {
self.txPending[hash] = struct{}{}
}
if len(self.txPending) > 0 {
txs := make(types.Transactions, len(self.txPending))
i := 0
for hash, _ := range self.txPending {
txs[i] = self.txSent[hash].tx
i++
}
self.send(txs, 1)
}
}
func (self *LesTxRelay) Discard(hashes []common.Hash) {
self.lock.Lock()
defer self.lock.Unlock()
for _, hash := range hashes {
delete(self.txSent, hash)
delete(self.txPending, hash)
}
}

View file

@ -70,7 +70,7 @@ type LightChain struct {
// NewLightChain returns a fully initialised light chain using information // NewLightChain returns a fully initialised light chain using information
// available in the database. It initialises the default Ethereum header // available in the database. It initialises the default Ethereum header
// validator. // validator.
func NewLightChain(odr OdrBackend, pow pow.PoW, mux *event.TypeMux) (*LightChain, error) { func NewLightChain(odr OdrBackend, config *core.ChainConfig, pow pow.PoW, mux *event.TypeMux) (*LightChain, error) {
bodyCache, _ := lru.New(bodyCacheLimit) bodyCache, _ := lru.New(bodyCacheLimit)
bodyRLPCache, _ := lru.New(bodyCacheLimit) bodyRLPCache, _ := lru.New(bodyCacheLimit)
blockCache, _ := lru.New(blockCacheLimit) blockCache, _ := lru.New(blockCacheLimit)
@ -87,8 +87,8 @@ func NewLightChain(odr OdrBackend, pow pow.PoW, mux *event.TypeMux) (*LightChain
} }
var err error var err error
bc.hc, err = core.NewHeaderChain(odr.Database(), bc.Validator, bc.getProcInterrupt) bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.Validator, bc.getProcInterrupt)
bc.SetValidator(core.NewHeaderValidator(bc.hc, pow)) bc.SetValidator(core.NewHeaderValidator(config, bc.hc, pow))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -106,7 +106,7 @@ func NewLightChain(odr OdrBackend, pow pow.PoW, mux *event.TypeMux) (*LightChain
} }
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
for hash, _ := range core.BadHashes { for hash, _ := range core.BadHashes {
if header := bc.GetHeader(hash); header != nil { if header := bc.GetHeaderByHash(hash); header != nil {
glog.V(logger.Error).Infof("Found bad hash, rewinding chain to block #%d [%x…]", header.Number, header.ParentHash[:4]) glog.V(logger.Error).Infof("Found bad hash, rewinding chain to block #%d [%x…]", header.Number, header.ParentHash[:4])
bc.SetHead(header.Number.Uint64() - 1) bc.SetHead(header.Number.Uint64() - 1)
glog.V(logger.Error).Infoln("Chain rewind was successful, resuming normal operation") glog.V(logger.Error).Infoln("Chain rewind was successful, resuming normal operation")
@ -131,13 +131,14 @@ func (self *LightChain) loadLastState() error {
// Corrupt or empty database, init from scratch // Corrupt or empty database, init from scratch
self.Reset() self.Reset()
} else { } else {
if header := self.GetHeader(head); header != nil { if header := self.GetHeaderByHash(head); header != nil {
self.hc.SetCurrentHeader(header) self.hc.SetCurrentHeader(header)
} }
} }
// Issue a status log and return // Issue a status log and return
headerTd := self.GetTd(self.hc.CurrentHeader().Hash()) header := self.hc.CurrentHeader()
headerTd := self.GetTd(header.Hash(), header.Number.Uint64())
glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.hc.CurrentHeader().Number, self.hc.CurrentHeader().Hash().Bytes()[:4], headerTd) glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.hc.CurrentHeader().Number, self.hc.CurrentHeader().Hash().Bytes()[:4], headerTd)
return nil return nil
@ -149,7 +150,7 @@ func (bc *LightChain) SetHead(head uint64) {
bc.mu.Lock() bc.mu.Lock()
defer bc.mu.Unlock() defer bc.mu.Unlock()
bc.hc.SetHead(head) bc.hc.SetHead(head, nil)
bc.loadLastState() bc.loadLastState()
} }
@ -175,8 +176,9 @@ func (self *LightChain) Status() (td *big.Int, currentBlock common.Hash, genesis
self.mu.RLock() self.mu.RLock()
defer self.mu.RUnlock() defer self.mu.RUnlock()
hash := self.hc.CurrentHeader().Hash() header := self.hc.CurrentHeader()
return self.GetTd(hash), hash, self.genesisBlock.Hash() hash := header.Hash()
return self.GetTd(hash, header.Number.Uint64()), hash, self.genesisBlock.Hash()
} }
// SetValidator sets the validator which is used to validate incoming headers. // SetValidator sets the validator which is used to validate incoming headers.
@ -213,7 +215,7 @@ func (bc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
defer bc.mu.Unlock() defer bc.mu.Unlock()
// Prepare the genesis block and reinitialise the chain // Prepare the genesis block and reinitialise the chain
if err := core.WriteTd(bc.chainDb, genesis.Hash(), genesis.Difficulty()); err != nil { if err := core.WriteTd(bc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil {
glog.Fatalf("failed to write genesis block TD: %v", err) glog.Fatalf("failed to write genesis block TD: %v", err)
} }
if err := core.WriteBlock(bc.chainDb, genesis); err != nil { if err := core.WriteBlock(bc.chainDb, genesis); err != nil {
@ -239,7 +241,7 @@ func (self *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.B
body := cached.(*types.Body) body := cached.(*types.Body)
return body, nil return body, nil
} }
body, err := GetBody(ctx, self.odr, hash) body, err := GetBody(ctx, self.odr, hash, self.hc.GetBlockNumber(hash))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -255,7 +257,7 @@ func (self *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.R
if cached, ok := self.bodyRLPCache.Get(hash); ok { if cached, ok := self.bodyRLPCache.Get(hash); ok {
return cached.(rlp.RawValue), nil return cached.(rlp.RawValue), nil
} }
body, err := GetBodyRLP(ctx, self.odr, hash) body, err := GetBodyRLP(ctx, self.odr, hash, self.hc.GetBlockNumber(hash))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -267,18 +269,18 @@ func (self *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.R
// HasBlock checks if a block is fully present in the database or not, caching // HasBlock checks if a block is fully present in the database or not, caching
// it if present. // it if present.
func (bc *LightChain) HasBlock(hash common.Hash) bool { func (bc *LightChain) HasBlock(hash common.Hash) bool {
blk, _ := bc.GetBlock(NoOdr, hash) blk, _ := bc.GetBlockByHash(NoOdr, hash)
return blk != nil return blk != nil
} }
// GetBlock retrieves a block from the database or ODR service by hash, // GetBlock retrieves a block from the database or ODR service by hash and number,
// caching it if found. // caching it if found.
func (self *LightChain) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) { func (self *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) {
// Short circuit if the block's already in the cache, retrieve otherwise // Short circuit if the block's already in the cache, retrieve otherwise
if block, ok := self.blockCache.Get(hash); ok { if block, ok := self.blockCache.Get(hash); ok {
return block.(*types.Block), nil return block.(*types.Block), nil
} }
block, err := GetBlock(ctx, self.odr, hash) block, err := GetBlock(ctx, self.odr, hash, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -287,6 +289,12 @@ func (self *LightChain) GetBlock(ctx context.Context, hash common.Hash) (*types.
return block, nil return block, nil
} }
// GetBlockByHash retrieves a block from the database or ODR service by hash,
// caching it if found.
func (self *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return self.GetBlock(ctx, hash, self.hc.GetBlockNumber(hash))
}
// GetBlockByNumber retrieves a block from the database or ODR service by // GetBlockByNumber retrieves a block from the database or ODR service by
// number, caching it (associated with its hash) if found. // number, caching it (associated with its hash) if found.
func (self *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { func (self *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) {
@ -294,7 +302,7 @@ func (self *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*t
if hash == (common.Hash{}) { if hash == (common.Hash{}) {
return nil, nil return nil, nil
} }
return self.GetBlock(ctx, hash) return self.GetBlock(ctx, hash, number)
} }
// Stop stops the blockchain service. If any imports are currently in progress // Stop stops the blockchain service. If any imports are currently in progress
@ -320,8 +328,8 @@ func (self *LightChain) Rollback(chain []common.Hash) {
for i := len(chain) - 1; i >= 0; i-- { for i := len(chain) - 1; i >= 0; i-- {
hash := chain[i] hash := chain[i]
if self.hc.CurrentHeader().Hash() == hash { if head := self.hc.CurrentHeader(); head.Hash() == hash {
self.hc.SetCurrentHeader(self.GetHeader(self.hc.CurrentHeader().ParentHash)) self.hc.SetCurrentHeader(self.GetHeader(head.ParentHash, head.Number.Uint64()-1))
} }
} }
} }
@ -400,15 +408,27 @@ func (self *LightChain) CurrentHeader() *types.Header {
} }
// GetTd retrieves a block's total difficulty in the canonical chain from the // GetTd retrieves a block's total difficulty in the canonical chain from the
// database by hash, caching it if found. // database by hash and number, caching it if found.
func (self *LightChain) GetTd(hash common.Hash) *big.Int { func (self *LightChain) GetTd(hash common.Hash, number uint64) *big.Int {
return self.hc.GetTd(hash) return self.hc.GetTd(hash, number)
} }
// GetHeader retrieves a block header from the database by hash, caching it if // GetTdByHash retrieves a block's total difficulty in the canonical chain from the
// database by hash, caching it if found.
func (self *LightChain) GetTdByHash(hash common.Hash) *big.Int {
return self.hc.GetTdByHash(hash)
}
// GetHeader retrieves a block header from the database by hash and number,
// caching it if found.
func (self *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header {
return self.hc.GetHeader(hash, number)
}
// GetHeaderByHash retrieves a block header from the database by hash, caching it if
// found. // found.
func (self *LightChain) GetHeader(hash common.Hash) *types.Header { func (self *LightChain) GetHeaderByHash(hash common.Hash) *types.Header {
return self.hc.GetHeader(hash) return self.hc.GetHeaderByHash(hash)
} }
// HasHeader checks if a block header is present in the database or not, caching // HasHeader checks if a block header is present in the database or not, caching

View file

@ -51,6 +51,10 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [
return headers return headers
} }
func testChainConfig() *core.ChainConfig {
return &core.ChainConfig{HomesteadBlock: big.NewInt(0)}
}
// newCanonical creates a chain database, and injects a deterministic canonical // newCanonical creates a chain database, and injects a deterministic canonical
// chain. Depending on the full flag, if creates either a full block chain or a // chain. Depending on the full flag, if creates either a full block chain or a
// header only chain. // header only chain.
@ -62,7 +66,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) {
// Initialize a fresh chain with only a genesis block // Initialize a fresh chain with only a genesis block
genesis, _ := core.WriteTestNetGenesisBlock(db) genesis, _ := core.WriteTestNetGenesisBlock(db)
blockchain, _ := NewLightChain(&dummyOdr{db: db}, core.FakePow{}, evmux) blockchain, _ := NewLightChain(&dummyOdr{db: db}, testChainConfig(), core.FakePow{}, evmux)
// Create and inject the requested chain // Create and inject the requested chain
if n == 0 { if n == 0 {
return db, blockchain, nil return db, blockchain, nil
@ -85,7 +89,7 @@ func thePow() pow.PoW {
func theLightChain(db ethdb.Database, t *testing.T) *LightChain { func theLightChain(db ethdb.Database, t *testing.T) *LightChain {
var eventMux event.TypeMux var eventMux event.TypeMux
core.WriteTestNetGenesisBlock(db) core.WriteTestNetGenesisBlock(db)
LightChain, err := NewLightChain(&dummyOdr{db: db}, thePow(), &eventMux) LightChain, err := NewLightChain(&dummyOdr{db: db}, testChainConfig(), thePow(), &eventMux)
if err != nil { if err != nil {
t.Error("failed creating LightChain:", err) t.Error("failed creating LightChain:", err)
t.FailNow() t.FailNow()
@ -120,11 +124,11 @@ func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td
// Sanity check that the forked chain can be imported into the original // Sanity check that the forked chain can be imported into the original
var tdPre, tdPost *big.Int var tdPre, tdPost *big.Int
tdPre = LightChain.GetTd(LightChain.CurrentHeader().Hash()) tdPre = LightChain.GetTdByHash(LightChain.CurrentHeader().Hash())
if err := testHeaderChainImport(headerChainB, LightChain); err != nil { if err := testHeaderChainImport(headerChainB, LightChain); err != nil {
t.Fatalf("failed to import forked header chain: %v", err) t.Fatalf("failed to import forked header chain: %v", err)
} }
tdPost = LightChain.GetTd(headerChainB[len(headerChainB)-1].Hash()) tdPost = LightChain.GetTdByHash(headerChainB[len(headerChainB)-1].Hash())
// Compare the total difficulties of the chains // Compare the total difficulties of the chains
comparator(tdPre, tdPost) comparator(tdPre, tdPost)
} }
@ -141,12 +145,12 @@ func printChain(bc *LightChain) {
func testHeaderChainImport(chain []*types.Header, LightChain *LightChain) error { func testHeaderChainImport(chain []*types.Header, LightChain *LightChain) error {
for _, header := range chain { for _, header := range chain {
// Try and validate the header // Try and validate the header
if err := LightChain.Validator().ValidateHeader(header, LightChain.GetHeader(header.ParentHash), false); err != nil { if err := LightChain.Validator().ValidateHeader(header, LightChain.GetHeaderByHash(header.ParentHash), false); err != nil {
return err return err
} }
// Manually insert the header into the database, but don't reorganize (allows subsequent testing) // Manually insert the header into the database, but don't reorganize (allows subsequent testing)
LightChain.mu.Lock() LightChain.mu.Lock()
core.WriteTd(LightChain.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, LightChain.GetTd(header.ParentHash))) core.WriteTd(LightChain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, LightChain.GetTdByHash(header.ParentHash)))
core.WriteHeader(LightChain.chainDb, header) core.WriteHeader(LightChain.chainDb, header)
LightChain.mu.Unlock() LightChain.mu.Unlock()
} }
@ -307,7 +311,7 @@ func chm(genesis *types.Block, db ethdb.Database) *LightChain {
odr := &dummyOdr{db: db} odr := &dummyOdr{db: db}
var eventMux event.TypeMux var eventMux event.TypeMux
bc := &LightChain{odr: odr, chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: core.FakePow{}} bc := &LightChain{odr: odr, chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: core.FakePow{}}
bc.hc, _ = core.NewHeaderChain(db, bc.Validator, bc.getProcInterrupt) bc.hc, _ = core.NewHeaderChain(db, testChainConfig(), bc.Validator, bc.getProcInterrupt)
bc.bodyCache, _ = lru.New(100) bc.bodyCache, _ = lru.New(100)
bc.bodyRLPCache, _ = lru.New(100) bc.bodyRLPCache, _ = lru.New(100)
bc.blockCache, _ = lru.New(100) bc.blockCache, _ = lru.New(100)
@ -347,7 +351,7 @@ func testReorg(t *testing.T, first, second []int, td int64) {
} }
// Make sure the chain total difficulty is the correct one // Make sure the chain total difficulty is the correct one
want := new(big.Int).Add(genesis.Difficulty(), big.NewInt(td)) want := new(big.Int).Add(genesis.Difficulty(), big.NewInt(td))
if have := bc.GetTd(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 { if have := bc.GetTdByHash(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 {
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
} }
} }
@ -389,7 +393,7 @@ func TestReorgBadHeaderHashes(t *testing.T) {
core.BadHashes[headers[3].Hash()] = true core.BadHashes[headers[3].Hash()] = true
defer func() { delete(core.BadHashes, headers[3].Hash()) }() defer func() { delete(core.BadHashes, headers[3].Hash()) }()
// Create a new chain manager and check it rolled back the state // Create a new chain manager and check it rolled back the state
ncm, err := NewLightChain(&dummyOdr{db: db}, core.FakePow{}, new(event.TypeMux)) ncm, err := NewLightChain(&dummyOdr{db: db}, testChainConfig(), core.FakePow{}, new(event.TypeMux))
if err != nil { if err != nil {
t.Fatalf("failed to create new chain manager: %v", err) t.Fatalf("failed to create new chain manager: %v", err)
} }

View file

@ -45,17 +45,17 @@ type OdrRequest interface {
// TrieID identifies a state or account storage trie // TrieID identifies a state or account storage trie
type TrieID struct { type TrieID struct {
blockHash, root common.Hash BlockHash, Root common.Hash
accKey []byte AccKey []byte
} }
// StateTrieID returns a TrieID for a state trie belonging to a certain block // StateTrieID returns a TrieID for a state trie belonging to a certain block
// header. // header.
func StateTrieID(header *types.Header) *TrieID { func StateTrieID(header *types.Header) *TrieID {
return &TrieID{ return &TrieID{
blockHash: header.Hash(), BlockHash: header.Hash(),
accKey: nil, AccKey: nil,
root: header.Root, Root: header.Root,
} }
} }
@ -64,9 +64,9 @@ func StateTrieID(header *types.Header) *TrieID {
// checking Merkle proofs. // checking Merkle proofs.
func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID { func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID {
return &TrieID{ return &TrieID{
blockHash: state.blockHash, BlockHash: state.BlockHash,
accKey: crypto.Keccak256(addr[:]), AccKey: crypto.Keccak256(addr[:]),
root: root, Root: root,
} }
} }
@ -111,22 +111,24 @@ func (req *CodeRequest) StoreResult(db ethdb.Database) {
type BlockRequest struct { type BlockRequest struct {
OdrRequest OdrRequest
Hash common.Hash Hash common.Hash
Number uint64
Rlp []byte Rlp []byte
} }
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database
func (req *BlockRequest) StoreResult(db ethdb.Database) { func (req *BlockRequest) StoreResult(db ethdb.Database) {
core.WriteBodyRLP(db, req.Hash, req.Rlp) core.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp)
} }
// ReceiptsRequest is the ODR request type for retrieving block bodies // ReceiptsRequest is the ODR request type for retrieving block bodies
type ReceiptsRequest struct { type ReceiptsRequest struct {
OdrRequest OdrRequest
Hash common.Hash Hash common.Hash
Number uint64
Receipts types.Receipts Receipts types.Receipts
} }
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database
func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
core.WriteBlockReceipts(db, req.Hash, req.Receipts) core.WriteBlockReceipts(db, req.Hash, req.Number, req.Receipts)
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -52,11 +53,11 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
} }
switch req := req.(type) { switch req := req.(type) {
case *BlockRequest: case *BlockRequest:
req.Rlp = core.GetBodyRLP(odr.sdb, req.Hash) req.Rlp = core.GetBodyRLP(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash))
case *ReceiptsRequest: case *ReceiptsRequest:
req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash) req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash))
case *TrieRequest: case *TrieRequest:
t, _ := trie.New(req.Id.root, odr.sdb) t, _ := trie.New(req.Id.Root, odr.sdb)
req.Proof = t.Prove(req.Key) req.Proof = t.Prove(req.Key)
trie.ClearGlobalCache() trie.ClearGlobalCache()
case *CodeRequest: case *CodeRequest:
@ -73,9 +74,9 @@ func TestOdrGetBlockLes1(t *testing.T) { testChainOdr(t, 1, 1, odrGetBlock) }
func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte {
var block *types.Block var block *types.Block
if bc != nil { if bc != nil {
block = bc.GetBlock(bhash) block = bc.GetBlockByHash(bhash)
} else { } else {
block, _ = lc.GetBlock(ctx, bhash) block, _ = lc.GetBlockByHash(ctx, bhash)
} }
if block == nil { if block == nil {
return nil return nil
@ -89,9 +90,9 @@ func TestOdrGetReceiptsLes1(t *testing.T) { testChainOdr(t, 1, 1, odrGetReceipts
func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte {
var receipts types.Receipts var receipts types.Receipts
if bc != nil { if bc != nil {
receipts = core.GetBlockReceipts(db, bhash) receipts = core.GetBlockReceipts(db, bhash, core.GetBlockNumber(db, bhash))
} else { } else {
receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash) receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, core.GetBlockNumber(db, bhash))
} }
if receipts == nil { if receipts == nil {
return nil return nil
@ -111,7 +112,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc
var res []byte var res []byte
for _, addr := range acc { for _, addr := range acc {
if bc != nil { if bc != nil {
header := bc.GetHeader(bhash) header := bc.GetHeaderByHash(bhash)
st, err := state.New(header.Root, db) st, err := state.New(header.Root, db)
if err == nil { if err == nil {
bal := st.GetBalance(addr) bal := st.GetBalance(addr)
@ -119,7 +120,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc
res = append(res, rlp...) res = append(res, rlp...)
} }
} else { } else {
header := lc.GetHeader(bhash) header := lc.GetHeaderByHash(bhash)
st := NewLightState(StateTrieID(header), lc.Odr()) st := NewLightState(StateTrieID(header), lc.Odr())
bal, err := st.GetBalance(ctx, addr) bal, err := st.GetBalance(ctx, addr)
if err == nil { if err == nil {
@ -179,7 +180,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
data[35] = byte(i) data[35] = byte(i)
if bc != nil { if bc != nil {
header := bc.GetHeader(bhash) header := bc.GetHeaderByHash(bhash)
statedb, err := state.New(header.Root, db) statedb, err := state.New(header.Root, db)
if err == nil { if err == nil {
from := statedb.GetOrNewStateObject(testBankAddress) from := statedb.GetOrNewStateObject(testBankAddress)
@ -194,13 +195,13 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
to: &testContractAddr, to: &testContractAddr,
} }
vmenv := core.NewEnv(statedb, bc, msg, header) vmenv := core.NewEnv(statedb, testChainConfig(), bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig) gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp) ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...) res = append(res, ret...)
} }
} else { } else {
header := lc.GetHeader(bhash) header := lc.GetHeaderByHash(bhash)
state := NewLightState(StateTrieID(header), lc.Odr()) state := NewLightState(StateTrieID(header), lc.Odr())
from, err := state.GetOrNewStateObject(ctx, testBankAddress) from, err := state.GetOrNewStateObject(ctx, testBankAddress)
if err == nil { if err == nil {
@ -215,7 +216,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
to: &testContractAddr, to: &testContractAddr,
} }
vmenv := NewEnv(ctx, state, lc, msg, header) vmenv := NewEnv(ctx, state, testChainConfig(), lc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig) gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp) ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
if vmenv.Error() == nil { if vmenv.Error() == nil {
@ -241,7 +242,7 @@ func testChainGen(i int, block *core.BlockGen) {
nonce := block.TxNonce(acc1Addr) nonce := block.TxNonce(acc1Addr)
tx2, _ := types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key) tx2, _ := types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key)
nonce++ nonce++
tx3, _ := types.NewContractCreation(nonce, big.NewInt(0), big.NewInt(100000), big.NewInt(0), testContractCode).SignECDSA(acc1Key) tx3, _ := types.NewContractCreation(nonce, big.NewInt(0), big.NewInt(1000000), big.NewInt(0), testContractCode).SignECDSA(acc1Key)
testContractAddr = crypto.CreateAddress(acc1Addr, nonce) testContractAddr = crypto.CreateAddress(acc1Addr, nonce)
block.AddTx(tx1) block.AddTx(tx1)
block.AddTx(tx2) block.AddTx(tx2)
@ -277,14 +278,14 @@ func testChainOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
) )
core.WriteGenesisBlockForTesting(ldb, core.GenesisAccount{testBankAddress, testBankFunds}) core.WriteGenesisBlockForTesting(ldb, core.GenesisAccount{testBankAddress, testBankFunds})
// Assemble the test environment // Assemble the test environment
blockchain, _ := core.NewBlockChain(sdb, pow, evmux) blockchain, _ := core.NewBlockChain(sdb, testChainConfig(), pow, evmux)
gchain, _ := core.GenerateChain(genesis, sdb, 4, testChainGen) gchain, _ := core.GenerateChain(genesis, sdb, 4, testChainGen)
if _, err := blockchain.InsertChain(gchain); err != nil { if _, err := blockchain.InsertChain(gchain); err != nil {
panic(err) panic(err)
} }
odr := &testOdr{sdb: sdb, ldb: ldb} odr := &testOdr{sdb: sdb, ldb: ldb}
lightchain, _ := NewLightChain(odr, pow, evmux) lightchain, _ := NewLightChain(odr, testChainConfig(), pow, evmux)
lightchain.SetValidator(bproc{}) lightchain.SetValidator(bproc{})
headers := make([]*types.Header, len(gchain)) headers := make([]*types.Header, len(gchain))
for i, block := range gchain { for i, block := range gchain {

View file

@ -53,11 +53,11 @@ func retrieveContractCode(ctx context.Context, odr OdrBackend, id *TrieID, hash
} }
// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash) (rlp.RawValue, error) { func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) {
if data := core.GetBodyRLP(odr.Database(), hash); data != nil { if data := core.GetBodyRLP(odr.Database(), hash, number); data != nil {
return data, nil return data, nil
} }
r := &BlockRequest{Hash: hash} r := &BlockRequest{Hash: hash, Number: number}
if err := odr.Retrieve(ctx, r); err != nil { if err := odr.Retrieve(ctx, r); err != nil {
return nil, err return nil, err
} else { } else {
@ -67,8 +67,8 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash) (rlp.RawV
// GetBody retrieves the block body (transactons, uncles) corresponding to the // GetBody retrieves the block body (transactons, uncles) corresponding to the
// hash. // hash.
func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash) (*types.Body, error) { func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) {
data, err := GetBodyRLP(ctx, odr, hash) data, err := GetBodyRLP(ctx, odr, hash, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,13 +82,13 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash) (*types.Body
// GetBlock retrieves an entire block corresponding to the hash, assembling it // GetBlock retrieves an entire block corresponding to the hash, assembling it
// back from the stored header and body. // back from the stored header and body.
func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash) (*types.Block, error) { func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) {
// Retrieve the block header and body contents // Retrieve the block header and body contents
header := core.GetHeader(odr.Database(), hash) header := core.GetHeader(odr.Database(), hash, number)
if header == nil { if header == nil {
return nil, ErrNoHeader return nil, ErrNoHeader
} }
body, err := GetBody(ctx, odr, hash) body, err := GetBody(ctx, odr, hash, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,12 +98,12 @@ func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash) (*types.Blo
// GetBlockReceipts retrieves the receipts generated by the transactions included // GetBlockReceipts retrieves the receipts generated by the transactions included
// in a block given by its hash. // in a block given by its hash.
func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash) (types.Receipts, error) { func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
receipts := core.GetBlockReceipts(odr.Database(), hash) receipts := core.GetBlockReceipts(odr.Database(), hash, number)
if receipts != nil { if receipts != nil {
return receipts, nil return receipts, nil
} }
r := &ReceiptsRequest{Hash: hash} r := &ReceiptsRequest{Hash: hash, Number: number}
if err := odr.Retrieve(ctx, r); err != nil { if err := odr.Retrieve(ctx, r); err != nil {
return nil, err return nil, err
} else { } else {

View file

@ -40,7 +40,7 @@ func (self Code) String() string {
} }
// Storage is a memory map cache of a contract storage // Storage is a memory map cache of a contract storage
type Storage map[string]common.Hash type Storage map[common.Hash]common.Hash
// String returns a string representation of the storage cache // String returns a string representation of the storage cache
func (self Storage) String() (str string) { func (self Storage) String() (str string) {
@ -135,8 +135,7 @@ func (self *StateObject) Storage() Storage {
// GetState returns the storage value at the given address from either the cache // GetState returns the storage value at the given address from either the cache
// or the trie // or the trie
func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.Hash, error) { func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.Hash, error) {
strkey := key.Str() value, exists := self.storage[key]
value, exists := self.storage[strkey]
if !exists { if !exists {
var err error var err error
value, err = self.getAddr(ctx, key) value, err = self.getAddr(ctx, key)
@ -144,7 +143,7 @@ func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.
return common.Hash{}, err return common.Hash{}, err
} }
if (value != common.Hash{}) { if (value != common.Hash{}) {
self.storage[strkey] = value self.storage[key] = value
} }
} }
@ -153,7 +152,7 @@ func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.
// SetState sets the storage value at the given address // SetState sets the storage value at the given address
func (self *StateObject) SetState(k, value common.Hash) { func (self *StateObject) SetState(k, value common.Hash) {
self.storage[k.Str()] = value self.storage[k] = value
self.dirty = true self.dirty = true
} }
@ -238,13 +237,13 @@ func (self *StateObject) Nonce() uint64 {
return self.nonce return self.nonce
} }
// EachStorage calls a callback function for every key/value pair found // ForEachStorage calls a callback function for every key/value pair found
// in the local storage cache. Note that unlike core/state.StateObject, // in the local storage cache. Note that unlike core/state.StateObject,
// light.StateObject only returns cached values and doesn't download the // light.StateObject only returns cached values and doesn't download the
// entire storage tree. // entire storage tree.
func (self *StateObject) EachStorage(cb func(key, value []byte)) { func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) {
for h, v := range self.storage { for h, v := range self.storage {
cb([]byte(h), v.Bytes()) cb(h, v)
} }
} }

View file

@ -51,7 +51,7 @@ func makeTestState() (common.Hash, ethdb.Database) {
func TestLightStateOdr(t *testing.T) { func TestLightStateOdr(t *testing.T) {
root, sdb := makeTestState() root, sdb := makeTestState()
header := &types.Header{Root: root} header := &types.Header{Root: root, Number: big.NewInt(0)}
core.WriteHeader(sdb, header) core.WriteHeader(sdb, header)
ldb, _ := ethdb.NewMemDatabase() ldb, _ := ethdb.NewMemDatabase()
odr := &testOdr{sdb: sdb, ldb: ldb} odr := &testOdr{sdb: sdb, ldb: ldb}
@ -138,7 +138,7 @@ func TestLightStateOdr(t *testing.T) {
func TestLightStateSetCopy(t *testing.T) { func TestLightStateSetCopy(t *testing.T) {
root, sdb := makeTestState() root, sdb := makeTestState()
header := &types.Header{Root: root} header := &types.Header{Root: root, Number: big.NewInt(0)}
core.WriteHeader(sdb, header) core.WriteHeader(sdb, header)
ldb, _ := ethdb.NewMemDatabase() ldb, _ := ethdb.NewMemDatabase()
odr := &testOdr{sdb: sdb, ldb: ldb} odr := &testOdr{sdb: sdb, ldb: ldb}
@ -217,7 +217,7 @@ func TestLightStateSetCopy(t *testing.T) {
func TestLightStateDelete(t *testing.T) { func TestLightStateDelete(t *testing.T) {
root, sdb := makeTestState() root, sdb := makeTestState()
header := &types.Header{Root: root} header := &types.Header{Root: root, Number: big.NewInt(0)}
core.WriteHeader(sdb, header) core.WriteHeader(sdb, header)
ldb, _ := ethdb.NewMemDatabase() ldb, _ := ethdb.NewMemDatabase()
odr := &testOdr{sdb: sdb, ldb: ldb} odr := &testOdr{sdb: sdb, ldb: ldb}

View file

@ -78,7 +78,7 @@ func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error)
func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) {
err = t.do(ctx, key, func() (err error) { err = t.do(ctx, key, func() (err error) {
if t.trie == nil { if t.trie == nil {
t.trie, err = trie.NewSecure(t.id.root, t.db) t.trie, err = trie.NewSecure(t.id.Root, t.db)
} }
if err == nil { if err == nil {
res, err = t.trie.TryGet(key) res, err = t.trie.TryGet(key)
@ -97,7 +97,7 @@ func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error)
func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) {
err = t.do(ctx, key, func() (err error) { err = t.do(ctx, key, func() (err error) {
if t.trie == nil { if t.trie == nil {
t.trie, err = trie.NewSecure(t.id.root, t.db) t.trie, err = trie.NewSecure(t.id.Root, t.db)
} }
if err == nil { if err == nil {
err = t.trie.TryUpdate(key, value) err = t.trie.TryUpdate(key, value)
@ -111,7 +111,7 @@ func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) {
func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) {
err = t.do(ctx, key, func() (err error) { err = t.do(ctx, key, func() (err error) {
if t.trie == nil { if t.trie == nil {
t.trie, err = trie.NewSecure(t.id.root, t.db) t.trie, err = trie.NewSecure(t.id.Root, t.db)
} }
if err == nil { if err == nil {
err = t.trie.TryDelete(key) err = t.trie.TryDelete(key)

View file

@ -28,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -43,6 +42,7 @@ var txPermanent = uint64(500)
// always receive all locally signed transactions in the same order as they are // always receive all locally signed transactions in the same order as they are
// created. // created.
type TxPool struct { type TxPool struct {
config *core.ChainConfig
quit chan bool quit chan bool
eventMux *event.TypeMux eventMux *event.TypeMux
events event.Subscription events event.Subscription
@ -76,8 +76,9 @@ type TxRelayBackend interface {
} }
// NewTxPool creates a new light transaction pool // NewTxPool creates a new light transaction pool
func NewTxPool(eventMux *event.TypeMux, chain *LightChain, relay TxRelayBackend) *TxPool { func NewTxPool(config *core.ChainConfig, eventMux *event.TypeMux, chain *LightChain, relay TxRelayBackend) *TxPool {
pool := &TxPool{ pool := &TxPool{
config: config,
nonce: make(map[common.Address]uint64), nonce: make(map[common.Address]uint64),
pending: make(map[common.Hash]*types.Transaction), pending: make(map[common.Hash]*types.Transaction),
mined: make(map[common.Hash][]*types.Transaction), mined: make(map[common.Hash][]*types.Transaction),
@ -170,7 +171,7 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, idx uin
return nil return nil
} }
receipts, err := GetBlockReceipts(ctx, pool.odr, hash) receipts, err := GetBlockReceipts(ctx, pool.odr, hash, idx)
if err != nil { if err != nil {
return err return err
} }
@ -213,18 +214,18 @@ func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
// possible to continue checking the missing blocks at the next chain head event // possible to continue checking the missing blocks at the next chain head event
func (pool *TxPool) setNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) { func (pool *TxPool) setNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) {
txc := make(txStateChanges) txc := make(txStateChanges)
oldh := pool.chain.GetHeader(pool.head) oldh := pool.chain.GetHeaderByHash(pool.head)
newh := newHeader newh := newHeader
// find common ancestor, create list of rolled back and new block hashes // find common ancestor, create list of rolled back and new block hashes
var oldHashes, newHashes []common.Hash var oldHashes, newHashes []common.Hash
for oldh.Hash() != newh.Hash() { for oldh.Hash() != newh.Hash() {
if oldh.GetNumberU64() >= newh.GetNumberU64() { if oldh.GetNumberU64() >= newh.GetNumberU64() {
oldHashes = append(oldHashes, oldh.Hash()) oldHashes = append(oldHashes, oldh.Hash())
oldh = pool.chain.GetHeader(oldh.ParentHash) oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1)
} }
if oldh.GetNumberU64() < newh.GetNumberU64() { if oldh.GetNumberU64() < newh.GetNumberU64() {
newHashes = append(newHashes, newh.Hash()) newHashes = append(newHashes, newh.Hash())
newh = pool.chain.GetHeader(newh.ParentHash) newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1)
} }
} }
if oldh.GetNumberU64() < pool.clearIdx { if oldh.GetNumberU64() < pool.clearIdx {
@ -280,7 +281,7 @@ func (pool *TxPool) eventLoop() {
txc, _ := pool.setNewHead(ctx, head) txc, _ := pool.setNewHead(ctx, head)
m, r := txc.getLists() m, r := txc.getLists()
pool.relay.NewHead(pool.head, m, r) pool.relay.NewHead(pool.head, m, r)
pool.homestead = params.IsHomestead(head.Number) pool.homestead = pool.config.IsHomestead(head.Number)
pool.mu.Unlock() pool.mu.Unlock()
} }
} }
@ -338,7 +339,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
// Check the transaction doesn't exceed the current // Check the transaction doesn't exceed the current
// block limit gas. // block limit gas.
header := pool.chain.GetHeader(pool.head) header := pool.chain.GetHeaderByHash(pool.head)
if header.GasLimit.Cmp(tx.Gas()) < 0 { if header.GasLimit.Cmp(tx.Gas()) < 0 {
return core.ErrGasLimit return core.ErrGasLimit
} }

View file

@ -86,7 +86,7 @@ func TestTxPool(t *testing.T) {
) )
core.WriteGenesisBlockForTesting(ldb, core.GenesisAccount{testBankAddress, testBankFunds}) core.WriteGenesisBlockForTesting(ldb, core.GenesisAccount{testBankAddress, testBankFunds})
// Assemble the test environment // Assemble the test environment
blockchain, _ := core.NewBlockChain(sdb, pow, evmux) blockchain, _ := core.NewBlockChain(sdb, testChainConfig(), pow, evmux)
gchain, _ := core.GenerateChain(genesis, sdb, poolTestBlocks, txPoolTestChainGen) gchain, _ := core.GenerateChain(genesis, sdb, poolTestBlocks, txPoolTestChainGen)
if _, err := blockchain.InsertChain(gchain); err != nil { if _, err := blockchain.InsertChain(gchain); err != nil {
panic(err) panic(err)
@ -94,10 +94,10 @@ func TestTxPool(t *testing.T) {
odr := &testOdr{sdb: sdb, ldb: ldb} odr := &testOdr{sdb: sdb, ldb: ldb}
relay := &testTxRelay{} relay := &testTxRelay{}
lightchain, _ := NewLightChain(odr, pow, evmux) lightchain, _ := NewLightChain(odr, testChainConfig(), pow, evmux)
lightchain.SetValidator(bproc{}) lightchain.SetValidator(bproc{})
txPermanent = 50 txPermanent = 50
pool := NewTxPool(evmux, lightchain, relay) pool := NewTxPool(testChainConfig(), evmux, lightchain, relay)
for ii, block := range gchain { for ii, block := range gchain {
i := ii + 1 i := ii + 1
@ -120,7 +120,7 @@ func TestTxPool(t *testing.T) {
if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}, 1); err != nil { if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}, 1); err != nil {
panic(err) panic(err)
} }
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 30)
got := relay.nhMined got := relay.nhMined
exp := minedTx(i) - minedTx(i-1) exp := minedTx(i) - minedTx(i-1)

View file

@ -33,6 +33,8 @@ import (
type VMEnv struct { type VMEnv struct {
vm.Environment vm.Environment
ctx context.Context ctx context.Context
chainConfig *core.ChainConfig
evm *vm.EVM
state *VMState state *VMState
header *types.Header header *types.Header
msg core.Message msg core.Message
@ -45,17 +47,27 @@ type VMEnv struct {
} }
// NewEnv creates a new execution environment based on an ODR capable light state // NewEnv creates a new execution environment based on an ODR capable light state
func NewEnv(ctx context.Context, state *LightState, chain *LightChain, msg core.Message, header *types.Header) *VMEnv { func NewEnv(ctx context.Context, state *LightState, chainConfig *core.ChainConfig, chain *LightChain, msg core.Message, header *types.Header, cfg vm.Config) *VMEnv {
env := &VMEnv{ env := &VMEnv{
chainConfig: chainConfig,
chain: chain, chain: chain,
header: header, header: header,
msg: msg, msg: msg,
typ: vm.StdVmTy, typ: vm.StdVmTy,
} }
env.state = &VMState{ctx: ctx, state: state, env: env} env.state = &VMState{ctx: ctx, state: state, env: env}
// if no log collector is present set self as the collector
if cfg.Logger.Collector == nil {
cfg.Logger.Collector = env
}
env.evm = vm.New(env, cfg)
return env return env
} }
func (self *VMEnv) RuleSet() vm.RuleSet { return self.chainConfig }
func (self *VMEnv) Vm() vm.Vm { return self.evm }
func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f } func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f }
func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number } func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number }
func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase }
@ -69,7 +81,7 @@ func (self *VMEnv) SetDepth(i int) { self.depth = i }
func (self *VMEnv) VmType() vm.Type { return self.typ } func (self *VMEnv) VmType() vm.Type { return self.typ }
func (self *VMEnv) SetVmType(t vm.Type) { self.typ = t } func (self *VMEnv) SetVmType(t vm.Type) { self.typ = t }
func (self *VMEnv) GetHash(n uint64) common.Hash { func (self *VMEnv) GetHash(n uint64) common.Hash {
for header := self.chain.GetHeader(self.header.ParentHash); header != nil; header = self.chain.GetHeader(header.ParentHash) { for header := self.chain.GetHeader(self.header.ParentHash, self.header.Number.Uint64()-1); header != nil; header = self.chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
if header.GetNumberU64() == n { if header.GetNumberU64() == n {
return header.Hash() return header.Hash()
} }
@ -142,6 +154,10 @@ func (s *VMState) errHandler(err error) {
func (s *VMState) GetAccount(addr common.Address) vm.Account { func (s *VMState) GetAccount(addr common.Address) vm.Account {
so, err := s.state.GetStateObject(s.ctx, addr) so, err := s.state.GetStateObject(s.ctx, addr)
s.errHandler(err) s.errHandler(err)
if err != nil {
// return a dummy state object to avoid panics
so = s.state.newStateObject(addr)
}
return so return so
} }
@ -149,6 +165,10 @@ func (s *VMState) GetAccount(addr common.Address) vm.Account {
func (s *VMState) CreateAccount(addr common.Address) vm.Account { func (s *VMState) CreateAccount(addr common.Address) vm.Account {
so, err := s.state.CreateStateObject(s.ctx, addr) so, err := s.state.CreateStateObject(s.ctx, addr)
s.errHandler(err) s.errHandler(err)
if err != nil {
// return a dummy state object to avoid panics
so = s.state.newStateObject(addr)
}
return so return so
} }