Skip to content

Commit 48b70ec

Browse files
ryanschneiderkaralabe
authored andcommitted
cmd, eth: Add support for --whitelist <blocknum>=<hash>,... flag
* Rejects peers that respond with a different hash for any of the passed in block numbers. * Meant for emergency situations when the network forks unexpectedly.
1 parent c1e3fe6 commit 48b70ec

File tree

8 files changed

+67
-5
lines changed

8 files changed

+67
-5
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ var (
8787
utils.LightServFlag,
8888
utils.LightPeersFlag,
8989
utils.LightKDFFlag,
90+
utils.WhitelistFlag,
9091
utils.CacheFlag,
9192
utils.CacheDatabaseFlag,
9293
utils.CacheTrieFlag,

cmd/geth/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ var AppHelpFlagGroups = []flagGroup{
8181
utils.LightServFlag,
8282
utils.LightPeersFlag,
8383
utils.LightKDFFlag,
84+
utils.WhitelistFlag,
8485
},
8586
},
8687
{

cmd/utils/flags.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ var (
182182
Name: "lightkdf",
183183
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
184184
}
185+
WhitelistFlag = cli.StringFlag{
186+
Name: "whitelist",
187+
Usage: "Comma separated block number-to-hash mappings to enforce (<number>=<hash>)",
188+
}
185189
// Dashboard settings
186190
DashboardEnabledFlag = cli.BoolFlag{
187191
Name: metrics.DashboardEnabledFlag,
@@ -1072,6 +1076,34 @@ func setEthash(ctx *cli.Context, cfg *eth.Config) {
10721076
}
10731077
}
10741078

1079+
func setWhitelist(ctx *cli.Context, cfg *eth.Config) {
1080+
if ctx.GlobalIsSet(WhitelistFlag.Name) {
1081+
entries := strings.Split(ctx.String(WhitelistFlag.Name), ",")
1082+
whitelist := make(map[uint64]common.Hash)
1083+
for _, entry := range entries {
1084+
split := strings.SplitN(entry, "=", 2)
1085+
if len(split) != 2 {
1086+
Fatalf("invalid whitelist entry: %s", entry)
1087+
}
1088+
1089+
bn, err := strconv.ParseUint(split[0], 0, 64)
1090+
if err != nil {
1091+
Fatalf("Invalid whitelist block number %s: %v", split[0], err)
1092+
}
1093+
1094+
hash := common.Hash{}
1095+
err = hash.UnmarshalText([]byte(split[1]))
1096+
if err != nil {
1097+
Fatalf("Invalid whitelist hash %s: %v", split[1], err)
1098+
}
1099+
1100+
whitelist[bn] = hash
1101+
}
1102+
1103+
cfg.Whitelist = whitelist
1104+
}
1105+
}
1106+
10751107
// checkExclusive verifies that only a single instance of the provided flags was
10761108
// set by the user. Each flag might optionally be followed by a string type to
10771109
// specialize it further.
@@ -1137,6 +1169,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
11371169
setGPO(ctx, &cfg.GPO)
11381170
setTxPool(ctx, &cfg.TxPool)
11391171
setEthash(ctx, cfg)
1172+
setWhitelist(ctx, cfg)
11401173

11411174
if ctx.GlobalIsSet(SyncModeFlag.Name) {
11421175
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)

eth/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
173173
}
174174
eth.txPool = core.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
175175

176-
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
176+
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, config.Whitelist); err != nil {
177177
return nil, err
178178
}
179179

eth/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ type Config struct {
8787
SyncMode downloader.SyncMode
8888
NoPruning bool
8989

90+
// Whitelist of required block number -> hash values to accept
91+
Whitelist map[uint64]common.Hash `toml:"-"`
92+
9093
// Light client options
9194
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
9295
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers

eth/handler.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package eth
1818

1919
import (
20+
"bytes"
2021
"encoding/json"
2122
"errors"
2223
"fmt"
@@ -88,6 +89,8 @@ type ProtocolManager struct {
8889
txsSub event.Subscription
8990
minedBlockSub *event.TypeMuxSubscription
9091

92+
whitelist map[uint64]common.Hash
93+
9194
// channels for fetcher, syncer, txsyncLoop
9295
newPeerCh chan *peer
9396
txsyncCh chan *txsync
@@ -101,7 +104,7 @@ type ProtocolManager struct {
101104

102105
// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
103106
// with the Ethereum network.
104-
func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) {
107+
func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, whitelist map[uint64]common.Hash) (*ProtocolManager, error) {
105108
// Create the protocol manager with the base fields
106109
manager := &ProtocolManager{
107110
networkID: networkID,
@@ -110,6 +113,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
110113
blockchain: blockchain,
111114
chainconfig: config,
112115
peers: newPeerSet(),
116+
whitelist: whitelist,
113117
newPeerCh: make(chan *peer),
114118
noMorePeers: make(chan struct{}),
115119
txsyncCh: make(chan *txsync),
@@ -307,6 +311,16 @@ func (pm *ProtocolManager) handle(p *peer) error {
307311
}
308312
}()
309313
}
314+
315+
// If we have any explicit whitelist block hashes, request them
316+
for bn := range pm.whitelist {
317+
p.Log().Debug("Requesting whitelist block", "number", bn)
318+
if err := p.RequestHeadersByNumber(bn, 1, 0, false); err != nil {
319+
p.Log().Error("whitelist request failed", "err", err, "number", bn, "peer", p.id)
320+
return err
321+
}
322+
}
323+
310324
// main loop. handle incoming messages.
311325
for {
312326
if err := pm.handleMsg(p); err != nil {
@@ -452,6 +466,16 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
452466
// Filter out any explicitly requested headers, deliver the rest to the downloader
453467
filter := len(headers) == 1
454468
if filter {
469+
// Check for any responses not matching our whitelist
470+
if expected, ok := pm.whitelist[headers[0].Number.Uint64()]; ok {
471+
actual := headers[0].Hash()
472+
if !bytes.Equal(expected.Bytes(), actual.Bytes()) {
473+
p.Log().Info("Dropping peer with non-matching whitelist block", "number", headers[0].Number.Uint64(), "hash", actual, "expected", expected)
474+
return errors.New("whitelist block mismatch")
475+
}
476+
p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", expected)
477+
}
478+
455479
// If it's a potential DAO fork check, validate against the rules
456480
if p.forkDrop != nil && pm.chainconfig.DAOForkBlock.Cmp(headers[0].Number) == 0 {
457481
// Disable the fork drop timer

eth/handler_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool
478478
if err != nil {
479479
t.Fatalf("failed to create new blockchain: %v", err)
480480
}
481-
pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db)
481+
pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, nil)
482482
if err != nil {
483483
t.Fatalf("failed to start test protocol manager: %v", err)
484484
}
@@ -559,7 +559,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
559559
if err != nil {
560560
t.Fatalf("failed to create new blockchain: %v", err)
561561
}
562-
pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db)
562+
pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, nil)
563563
if err != nil {
564564
t.Fatalf("failed to start test protocol manager: %v", err)
565565
}

eth/helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
6666
panic(err)
6767
}
6868

69-
pm, err := NewProtocolManager(gspec.Config, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db)
69+
pm, err := NewProtocolManager(gspec.Config, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, nil)
7070
if err != nil {
7171
return nil, nil, err
7272
}

0 commit comments

Comments
 (0)