├── .gitignore ├── LICENSE ├── README.md ├── main.go └── protocol.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | manspreading -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xiaoyao Qian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manspreading 2 | Manspreading helps you greedily occupy a peer seat in a remote geth node. 3 | ![seat-occupied](https://sicksack.com/bags/bag-0261.jpg) 4 | 5 | ### Introduction 6 | Sadly, due to the fact that many nodes on Ethereum network do not change the [default](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) `--maxpeers` settings, most nodes are full and won't accept new peers. 7 | Although there are `static-nodes.json` and `trusted-nodes.json` where a node owner can hardcode peers that will always connect regardless of the restriction by `--maxpeers`, these options are buried deep in the document and codebase, so that few knows they exist. 8 | Preserving a node peer seat at a peer becomes essential in the development/research on geth. 9 | 10 | Manspreading is a proxy server that can be run as daemon and occupies a "seat" at a remote geth peer, so the real geth instance behind the proxy can stop and restart anytime without worrying the seat at the remote peer been taken during the restart period. 11 | 12 | ### Prerequisite 13 | - Golang (v1.8+) 14 | - You need to install [geth](https://github.com/ethereum/go-ethereum/) as the dependency by running `go get github.com/ethereum/go-ethereum` 15 | 16 | ### Usage 17 | 1. `go build .` 18 | 2. `./manspreading --upstream="" --listenaddr="127.0.0.1:36666"` 19 | A log line will show what's the enode url of the manspreading proxy in the format of `enode://@` 20 | or if you have a nodekey file that you'd like to use: 21 | `./manspreading --upstream="" --listenaddr="127.0.0.1:36666" --nodekey=""` 22 | Upstream node will be configured as both a static node and a trusted node, therefore even if the upstream disconnect itself, manspreading will attempt to reconnect indefinitely. 23 | 3. Start your real geth instance and add the manspreading enode url as a peer by running `admin.addPeer("")` 24 | 25 | ### TODO 26 | - Making manspreading a 1-to-many or even a many-to-many proxy -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "flag" 6 | "fmt" 7 | "math/big" 8 | "sync" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/core/types" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/ethereum/go-ethereum/log" 14 | "github.com/ethereum/go-ethereum/p2p" 15 | "github.com/ethereum/go-ethereum/p2p/discover" 16 | ) 17 | 18 | const ua = "manspreading" 19 | const ver = "1.0.0" 20 | 21 | // statusData is the network packet for the status message. 22 | type statusData struct { 23 | ProtocolVersion uint32 24 | NetworkId uint64 25 | TD *big.Int 26 | CurrentBlock common.Hash 27 | GenesisBlock common.Hash 28 | } 29 | 30 | // newBlockData is the network packet for the block propagation message. 31 | type newBlockData struct { 32 | Block *types.Block 33 | TD *big.Int 34 | } 35 | 36 | type conn struct { 37 | p *p2p.Peer 38 | rw p2p.MsgReadWriter 39 | } 40 | 41 | type proxy struct { 42 | lock sync.RWMutex 43 | upstreamNode *discover.Node 44 | upstreamConn *conn 45 | downstreamConn *conn 46 | upstreamState statusData 47 | srv *p2p.Server 48 | } 49 | 50 | var pxy *proxy 51 | 52 | var upstreamUrl = flag.String("upstream", "", "upstream enode url to connect to") 53 | var listenAddr = flag.String("listenaddr", "127.0.0.1:36666", "listening addr") 54 | var privkey = flag.String("nodekey", "", "nodekey file") 55 | 56 | func init() { 57 | flag.Parse() 58 | } 59 | 60 | func main() { 61 | var nodekey *ecdsa.PrivateKey 62 | if *privkey != "" { 63 | nodekey, _ = crypto.LoadECDSA(*privkey) 64 | fmt.Println("Node Key loaded from ", *privkey) 65 | } else { 66 | nodekey, _ = crypto.GenerateKey() 67 | crypto.SaveECDSA("./nodekey", nodekey) 68 | fmt.Println("Node Key generated and saved to ./nodekey") 69 | } 70 | 71 | node, _ := discover.ParseNode(*upstreamUrl) 72 | pxy = &proxy{ 73 | upstreamNode: node, 74 | } 75 | 76 | config := p2p.Config{ 77 | PrivateKey: nodekey, 78 | MaxPeers: 2, 79 | NoDiscovery: true, 80 | DiscoveryV5: false, 81 | Name: common.MakeName(fmt.Sprintf("%s/%s", ua, node.ID.String()), ver), 82 | BootstrapNodes: []*discover.Node{node}, 83 | StaticNodes: []*discover.Node{node}, 84 | TrustedNodes: []*discover.Node{node}, 85 | 86 | Protocols: []p2p.Protocol{newManspreadingProtocol()}, 87 | 88 | ListenAddr: *listenAddr, 89 | Logger: log.New(), 90 | } 91 | config.Logger.SetHandler(log.StdoutHandler) 92 | 93 | pxy.srv = &p2p.Server{Config: config} 94 | 95 | // Wait forever 96 | var wg sync.WaitGroup 97 | wg.Add(2) 98 | err := pxy.srv.Start() 99 | wg.Done() 100 | if err != nil { 101 | fmt.Println(err) 102 | } 103 | wg.Wait() 104 | } 105 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/ethereum/go-ethereum/eth" 8 | "github.com/ethereum/go-ethereum/p2p" 9 | "github.com/ethereum/go-ethereum/p2p/discover" 10 | "github.com/ethereum/go-ethereum/rlp" 11 | ) 12 | 13 | func newManspreadingProtocol() p2p.Protocol { 14 | return p2p.Protocol{ 15 | Name: eth.ProtocolName, 16 | Version: eth.ProtocolVersions[0], 17 | Length: eth.ProtocolLengths[0], 18 | Run: handle, 19 | NodeInfo: func() interface{} { 20 | fmt.Println("Noop: NodeInfo called") 21 | return nil 22 | }, 23 | PeerInfo: func(id discover.NodeID) interface{} { 24 | fmt.Println("Noop: PeerInfo called") 25 | return nil 26 | }, 27 | } 28 | } 29 | 30 | func handle(p *p2p.Peer, rw p2p.MsgReadWriter) error { 31 | fmt.Println("Run called") 32 | 33 | for { 34 | fmt.Println("Waiting for msg...") 35 | msg, err := rw.ReadMsg() 36 | fmt.Println("Got a msg from: ", fromWhom(p.ID().String())) 37 | if err != nil { 38 | fmt.Println("readMsg err: ", err) 39 | 40 | if err == io.EOF { 41 | fmt.Println(fromWhom(p.ID().String()), " has dropped its connection...") 42 | pxy.lock.Lock() 43 | if p.ID() == pxy.upstreamNode.ID { 44 | pxy.upstreamConn = nil 45 | } else { 46 | pxy.downstreamConn = nil 47 | } 48 | pxy.lock.Unlock() 49 | } 50 | 51 | return err 52 | } 53 | fmt.Println("msg.Code: ", msg.Code) 54 | 55 | if msg.Code == eth.StatusMsg { // handshake 56 | var myMessage statusData 57 | err = msg.Decode(&myMessage) 58 | if err != nil { 59 | fmt.Println("decode statusData err: ", err) 60 | return err 61 | } 62 | 63 | fmt.Println("ProtocolVersion: ", myMessage.ProtocolVersion) 64 | fmt.Println("NetworkId: ", myMessage.NetworkId) 65 | fmt.Println("TD: ", myMessage.TD) 66 | fmt.Println("CurrentBlock: ", myMessage.CurrentBlock.Hex()) 67 | fmt.Println("GenesisBlock: ", myMessage.GenesisBlock.Hex()) 68 | 69 | pxy.lock.Lock() 70 | if p.ID() == pxy.upstreamNode.ID { 71 | pxy.upstreamState = myMessage 72 | pxy.upstreamConn = &conn{p, rw} 73 | } else { 74 | pxy.downstreamConn = &conn{p, rw} 75 | } 76 | pxy.lock.Unlock() 77 | 78 | err = p2p.Send(rw, eth.StatusMsg, &statusData{ 79 | ProtocolVersion: myMessage.ProtocolVersion, 80 | NetworkId: myMessage.NetworkId, 81 | TD: pxy.upstreamState.TD, 82 | CurrentBlock: pxy.upstreamState.CurrentBlock, 83 | GenesisBlock: myMessage.GenesisBlock, 84 | }) 85 | 86 | if err != nil { 87 | fmt.Println("handshake err: ", err) 88 | return err 89 | } 90 | } else if msg.Code == eth.NewBlockMsg { 91 | var myMessage newBlockData 92 | err = msg.Decode(&myMessage) 93 | if err != nil { 94 | fmt.Println("decode newBlockMsg err: ", err) 95 | } 96 | 97 | pxy.lock.Lock() 98 | if p.ID() == pxy.upstreamNode.ID { 99 | pxy.upstreamState.CurrentBlock = myMessage.Block.Hash() 100 | pxy.upstreamState.TD = myMessage.TD 101 | } //TODO: handle newBlock from downstream 102 | pxy.lock.Unlock() 103 | 104 | // need to re-encode msg 105 | size, r, err := rlp.EncodeToReader(myMessage) 106 | if err != nil { 107 | fmt.Println("encoding newBlockMsg err: ", err) 108 | } 109 | relay(p, p2p.Msg{Code: eth.NewBlockMsg, Size: uint32(size), Payload: r}) 110 | } else { 111 | relay(p, msg) 112 | } 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func relay(p *p2p.Peer, msg p2p.Msg) { 119 | var err error 120 | pxy.lock.RLock() 121 | defer pxy.lock.RUnlock() 122 | if p.ID() != pxy.upstreamNode.ID && pxy.upstreamConn != nil { 123 | err = pxy.upstreamConn.rw.WriteMsg(msg) 124 | } else if p.ID() == pxy.upstreamNode.ID && pxy.downstreamConn != nil { 125 | err = pxy.downstreamConn.rw.WriteMsg(msg) 126 | } else { 127 | fmt.Println("One of upstream/downstream isn't alive: ", pxy.srv.Peers()) 128 | } 129 | 130 | if err != nil { 131 | fmt.Println("relaying err: ", err) 132 | } 133 | } 134 | 135 | func (pxy *proxy) upstreamAlive() bool { 136 | for _, peer := range pxy.srv.Peers() { 137 | if peer.ID() == pxy.upstreamNode.ID { 138 | return true 139 | } 140 | } 141 | return false 142 | } 143 | 144 | func fromWhom(nodeId string) string { 145 | if nodeId == pxy.upstreamNode.ID.String() { 146 | return "upstream" 147 | } else { 148 | return "downstream" 149 | } 150 | } 151 | --------------------------------------------------------------------------------