diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3422d9500b..ca86fb26f8 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -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, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ca9dd76fdf..792fcb41d1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -28,6 +28,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "github.com/codegangsta/cli" "github.com/ethereum/ethash" @@ -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)", } @@ -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 := ð.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, @@ -499,7 +518,6 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { glog.V(logger.Info).Infoln("dev mode enabled") } - return cfg } diff --git a/eth/backend.go b/eth/backend.go index 9ec3c14404..04dd3767ad 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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 @@ -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) diff --git a/eth/handler.go b/eth/handler.go index 3fc9096725..5716350af6 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -17,6 +17,7 @@ package eth import ( + "errors" "fmt" "math" "math/big" @@ -42,6 +43,10 @@ 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...)) } @@ -49,17 +54,8 @@ func errResp(code errCode, format string, v ...interface{}) error { 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 @@ -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, @@ -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], @@ -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) @@ -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) { diff --git a/eth/handler_test.go b/eth/handler_test.go index dde2ecbd51..8ab5c1aadb 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -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() @@ -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() @@ -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() @@ -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() @@ -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() @@ -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() @@ -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() diff --git a/eth/helper_test.go b/eth/helper_test.go index 9314884efb..bd65b49f89 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -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) @@ -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 } diff --git a/eth/protocol.go b/eth/protocol.go index 49f096a3b2..0d2b5128d9 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -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 @@ -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} diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 523e6c1eb8..bac519ae39 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -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() @@ -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() @@ -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.