Skip to content

Commit

Permalink
cmd, eth: support switching client modes of operation
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe committed Oct 19, 2015
1 parent 10ed107 commit 92f9a3e
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 35 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.DataDirFlag,
utils.BlockchainVersionFlag,
utils.OlympicFlag,
utils.EthModeFlag,
utils.EthVersionFlag,
utils.CacheFlag,
utils.JSpathFlag,
Expand Down
24 changes: 21 additions & 3 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"

"github.com/codegangsta/cli"
"github.com/ethereum/ethash"
Expand Down Expand Up @@ -148,9 +149,14 @@ var (
Name: "olympic",
Usage: "Use olympic style protocol",
}
EthModeFlag = cli.StringFlag{
Name: "mode",
Value: "archive",
Usage: "Client mode of operation (archive, full, light)",
}
EthVersionFlag = cli.IntFlag{
Name: "eth",
Value: 62,
Value: 63,
Usage: "Highest eth protocol to advertise (temporary, dev option)",
}

Expand Down Expand Up @@ -425,12 +431,25 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
if err != nil {
glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default")
}

// Resolve the mode of opeation from the string flag
var clientMode eth.Mode
switch strings.ToLower(ctx.GlobalString(EthModeFlag.Name)) {
case "archive":
clientMode = eth.ArchiveMode
case "full":
clientMode = eth.FullMode
case "light":
clientMode = eth.LightMode
default:
glog.Fatalf("Unknown node type requested: %s", ctx.GlobalString(EthModeFlag.Name))
}
// Assemble the entire eth configuration and return
cfg := &eth.Config{
Name: common.MakeName(clientID, version),
DataDir: MustDataDir(ctx),
GenesisNonce: ctx.GlobalInt(GenesisNonceFlag.Name),
GenesisFile: ctx.GlobalString(GenesisFileFlag.Name),
Mode: clientMode,
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
SkipBcVersionCheck: false,
Expand Down Expand Up @@ -499,7 +518,6 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {

glog.V(logger.Info).Infoln("dev mode enabled")
}

return cfg
}

Expand Down
6 changes: 4 additions & 2 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type Config struct {
GenesisFile string
GenesisBlock *types.Block // used by block tests
Olympic bool
Mode Mode

BlockChainVersion int
SkipBcVersionCheck bool // e.g. blockchain export
Expand Down Expand Up @@ -398,8 +399,9 @@ func New(config *Config) (*Ethereum, error) {

eth.blockProcessor = core.NewBlockProcessor(chainDb, eth.pow, eth.blockchain, eth.EventMux())
eth.blockchain.SetProcessor(eth.blockProcessor)
eth.protocolManager = NewProtocolManager(config.NetworkId, eth.eventMux, eth.txPool, eth.pow, eth.blockchain, chainDb)

if eth.protocolManager, err = NewProtocolManager(config.Mode, config.NetworkId, eth.eventMux, eth.txPool, eth.pow, eth.blockchain, chainDb); err != nil {
return nil, err
}
eth.miner = miner.New(eth, eth.EventMux(), eth.pow)
eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData)
Expand Down
40 changes: 22 additions & 18 deletions eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package eth

import (
"errors"
"fmt"
"math"
"math/big"
Expand All @@ -42,24 +43,19 @@ const (
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
)

// 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 blockFetcherFn func([]common.Hash) error

// extProt is an interface which is passed around so we can expose GetHashes and GetBlock without exposing it to the rest of the protocol
// extProt is passed around to peers which require to GetHashes and GetBlocks
type extProt struct {
getHashes hashFetcherFn
getBlocks blockFetcherFn
}

func (ep extProt) GetHashes(hash common.Hash) error { return ep.getHashes(hash) }
func (ep extProt) GetBlock(hashes []common.Hash) error { return ep.getBlocks(hashes) }

type ProtocolManager struct {
mode Mode
txpool txPool
blockchain *core.BlockChain
chaindb ethdb.Database
Expand Down Expand Up @@ -87,9 +83,10 @@ type ProtocolManager struct {

// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network.
func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow pow.PoW, blockchain *core.BlockChain, chaindb ethdb.Database) *ProtocolManager {
func NewProtocolManager(mode Mode, networkId int, mux *event.TypeMux, txpool txPool, pow pow.PoW, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) {
// Create the protocol manager with the base fields
manager := &ProtocolManager{
mode: mode,
eventMux: mux,
txpool: txpool,
blockchain: blockchain,
Expand All @@ -100,11 +97,15 @@ func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow po
quitSync: make(chan struct{}),
}
// Initiate a sub-protocol for every implemented version we can handle
manager.SubProtocols = make([]p2p.Protocol, len(ProtocolVersions))
for i := 0; i < len(manager.SubProtocols); i++ {
version := ProtocolVersions[i]

manager.SubProtocols[i] = p2p.Protocol{
manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions))
for i, version := range ProtocolVersions {
// Skip protocol version if incompatible with the mode of operation
if minimumProtocolVersion[mode] > version {
continue
}
// Compatible, initialize the sub-protocol
version := version // Closure for the run
manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
Name: "eth",
Version: version,
Length: ProtocolLengths[i],
Expand All @@ -113,7 +114,10 @@ func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow po
manager.newPeerCh <- peer
return manager.handle(peer)
},
}
})
}
if len(manager.SubProtocols) == 0 {
return nil, errIncompatibleConfig
}
// Construct the different synchronisation mechanisms
manager.downloader = downloader.New(manager.eventMux, manager.blockchain.HasBlock, manager.blockchain.GetBlock, manager.blockchain.CurrentBlock, manager.blockchain.GetTd, manager.blockchain.InsertChain, manager.removePeer)
Expand All @@ -126,7 +130,7 @@ func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow po
}
manager.fetcher = fetcher.New(manager.blockchain.GetBlock, validator, manager.BroadcastBlock, heighter, manager.blockchain.InsertChain, manager.removePeer)

return manager
return manager, nil
}

func (pm *ProtocolManager) removePeer(id string) {
Expand Down
44 changes: 37 additions & 7 deletions eth/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,42 @@ import (
"github.com/ethereum/go-ethereum/params"
)

// Tests that protocol versions and modes of operations are matched up properly.
func TestProtocolCompatibility(t *testing.T) {
// Define the compatibility chart
tests := []struct {
version uint
mode Mode
compatible bool
}{
{61, ArchiveMode, true}, {62, ArchiveMode, true}, {63, ArchiveMode, true}, {64, ArchiveMode, true},
{61, FullMode, false}, {62, FullMode, false}, {63, FullMode, true}, {64, FullMode, true},
{61, LightMode, false}, {62, LightMode, false}, {63, LightMode, false}, {64, LightMode, true},
}
// Make sure anything we screw up is restored
backup := ProtocolVersions
defer func() { ProtocolVersions = backup }()

// Try all available compatibility configs and check for errors
for i, tt := range tests {
ProtocolVersions = []uint{tt.version}

pm, err := newTestProtocolManager(tt.mode, 0, nil, nil)
if pm != nil {
defer pm.Stop()
}
if (err == nil && !tt.compatible) || (err != nil && tt.compatible) {
t.Errorf("test %d: compatibility mismatch: have error %v, want compatibility %v", i, err, tt.compatible)
}
}
}

// Tests that hashes can be retrieved from a remote chain by hashes in reverse
// order.
func TestGetBlockHashes61(t *testing.T) { testGetBlockHashes(t, 61) }

func testGetBlockHashes(t *testing.T, protocol int) {
pm := newTestProtocolManager(downloader.MaxHashFetch+15, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, downloader.MaxHashFetch+15, nil, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -65,7 +95,7 @@ func testGetBlockHashes(t *testing.T, protocol int) {
func TestGetBlockHashesFromNumber61(t *testing.T) { testGetBlockHashesFromNumber(t, 61) }

func testGetBlockHashesFromNumber(t *testing.T, protocol int) {
pm := newTestProtocolManager(downloader.MaxHashFetch+15, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, downloader.MaxHashFetch+15, nil, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -105,7 +135,7 @@ func testGetBlockHashesFromNumber(t *testing.T, protocol int) {
func TestGetBlocks61(t *testing.T) { testGetBlocks(t, 61) }

func testGetBlocks(t *testing.T, protocol int) {
pm := newTestProtocolManager(downloader.MaxHashFetch+15, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, downloader.MaxHashFetch+15, nil, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -177,7 +207,7 @@ func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) }
func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) }

func testGetBlockHeaders(t *testing.T, protocol int) {
pm := newTestProtocolManager(downloader.MaxHashFetch+15, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, downloader.MaxHashFetch+15, nil, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -303,7 +333,7 @@ func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) }
func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) }

func testGetBlockBodies(t *testing.T, protocol int) {
pm := newTestProtocolManager(downloader.MaxBlockFetch+15, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, downloader.MaxBlockFetch+15, nil, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -410,7 +440,7 @@ func testGetNodeData(t *testing.T, protocol int) {
}
}
// Assemble the test environment
pm := newTestProtocolManager(4, generator, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, 4, generator, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down Expand Up @@ -500,7 +530,7 @@ func testGetReceipt(t *testing.T, protocol int) {
}
}
// Assemble the test environment
pm := newTestProtocolManager(4, generator, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, 4, generator, nil)
peer, _ := newTestPeer("peer", protocol, pm, true)
defer peer.close()

Expand Down
19 changes: 17 additions & 2 deletions eth/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var (
// 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(blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) *ProtocolManager {
func newTestProtocolManager(mode Mode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, error) {
var (
evmux = new(event.TypeMux)
pow = new(core.FakePow)
Expand All @@ -42,8 +42,23 @@ func newTestProtocolManager(blocks int, generator func(int, *core.BlockGen), new
if _, err := blockchain.InsertChain(chain); err != nil {
panic(err)
}
pm := NewProtocolManager(NetworkId, evmux, &testTxPool{added: newtx}, pow, blockchain, db)
pm, err := NewProtocolManager(mode, NetworkId, evmux, &testTxPool{added: newtx}, pow, blockchain, db)
if err != nil {
return nil, err
}
pm.Start()
return pm, 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, mode Mode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) *ProtocolManager {
pm, err := newTestProtocolManager(mode, blocks, generator, newtx)
if err != nil {
t.Fatalf("Failed to create protocol manager: %v", err)
}
return pm
}

Expand Down
17 changes: 17 additions & 0 deletions eth/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)

// Mode represents the mode of operation of the eth client.
type Mode int

const (
ArchiveMode Mode = iota // Maintain the entire blockchain history
FullMode // Maintain only a recent view of the blockchain
LightMode // Don't maintain any history, rather fetch on demand
)

// Constants to match up protocol versions and messages
const (
eth61 = 61
Expand All @@ -34,6 +43,14 @@ const (
eth64 = 64
)

// minimumProtocolVersion is the minimum version of the protocol eth must run to
// support the desired mode of operation.
var minimumProtocolVersion = map[Mode]uint{
ArchiveMode: eth61,
FullMode: eth63,
LightMode: eth64,
}

// Supported versions of the eth protocol (first is primary).
var ProtocolVersions = []uint{eth64, eth63, eth62, eth61}

Expand Down
6 changes: 3 additions & 3 deletions eth/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestStatusMsgErrors63(t *testing.T) { testStatusMsgErrors(t, 63) }
func TestStatusMsgErrors64(t *testing.T) { testStatusMsgErrors(t, 64) }

func testStatusMsgErrors(t *testing.T, protocol int) {
pm := newTestProtocolManager(0, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, 0, nil, nil)
td, currentBlock, genesis := pm.blockchain.Status()
defer pm.Stop()

Expand Down Expand Up @@ -99,7 +99,7 @@ func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }

func testRecvTransactions(t *testing.T, protocol int) {
txAdded := make(chan []*types.Transaction)
pm := newTestProtocolManager(0, nil, txAdded)
pm := newTestProtocolManagerMust(t, ArchiveMode, 0, nil, txAdded)
p, _ := newTestPeer("peer", protocol, pm, true)
defer pm.Stop()
defer p.close()
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }

func testSendTransactions(t *testing.T, protocol int) {
pm := newTestProtocolManager(0, nil, nil)
pm := newTestProtocolManagerMust(t, ArchiveMode, 0, nil, nil)
defer pm.Stop()

// Fill the pool with big transactions.
Expand Down

0 comments on commit 92f9a3e

Please sign in to comment.