github.com/klaytn/klaytn@v1.12.1/node/api_test.go (about) 1 // Modifications Copyright 2021 The klaytn Authors 2 // Copyright 2015 The go-ethereum Authors 3 // This file is part of go-ethereum. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from node/api_test.go (2021/05/17). 19 // Modified and improved for the klaytn development. 20 21 package node 22 23 import ( 24 "bytes" 25 "io" 26 "net" 27 "net/http" 28 "net/url" 29 "testing" 30 31 "github.com/klaytn/klaytn/networks/rpc" 32 "github.com/stretchr/testify/assert" 33 ) 34 35 // This test uses the admin_startRPC and admin_startWS APIs, 36 // checking whether the HTTP server is started correctly. 37 type test struct { 38 name string 39 cfg Config 40 fn func(*testing.T, *Node, *PrivateAdminAPI) 41 42 // Checks. These run after the node is configured and all API calls have been made. 43 wantReachable bool // whether the HTTP server should be reachable at all 44 wantRPC bool // whether JSON-RPC/HTTP should be accessible 45 wantWS bool // whether JSON-RPC/WS should be accessible 46 } 47 48 func TestStartRPC(t *testing.T) { 49 tests := []test{ 50 { 51 name: "all off", 52 cfg: Config{}, 53 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 54 }, 55 wantReachable: false, 56 wantRPC: false, 57 wantWS: false, 58 }, 59 { 60 name: "rpc enabled through config", 61 cfg: Config{HTTPHost: "127.0.0.1"}, 62 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 63 }, 64 wantReachable: true, 65 wantRPC: true, 66 wantWS: false, 67 }, 68 { 69 name: "rpc enabled through API", 70 cfg: Config{}, 71 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 72 _, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil) 73 assert.NoError(t, err) 74 }, 75 wantReachable: true, 76 wantRPC: true, 77 wantWS: false, 78 }, 79 { 80 name: "rpc start again after failure", 81 cfg: Config{}, 82 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 83 // Listen on a random port. 84 listener, err := net.Listen("tcp", "127.0.0.1:0") 85 if err != nil { 86 t.Fatal("can't listen:", err) 87 } 88 defer listener.Close() 89 port := listener.Addr().(*net.TCPAddr).Port 90 91 // Now try to start RPC on that port. This should fail. 92 _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil) 93 if err == nil { 94 t.Fatal("StartHTTP should have failed on port", port) 95 } 96 97 // Try again after unblocking the port. It should work this time. 98 listener.Close() 99 _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil) 100 assert.NoError(t, err) 101 }, 102 wantReachable: true, 103 wantRPC: true, 104 wantWS: false, 105 }, 106 { 107 name: "rpc stopped through API", 108 cfg: Config{HTTPHost: "127.0.0.1"}, 109 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 110 _, err := api.StopHTTP() 111 assert.NoError(t, err) 112 }, 113 wantReachable: false, 114 wantRPC: false, 115 wantWS: false, 116 }, 117 { 118 name: "ws enabled through config", 119 cfg: Config{WSHost: "127.0.0.1"}, 120 wantReachable: false, 121 wantRPC: false, 122 wantWS: true, 123 }, 124 { 125 name: "ws enabled through API", 126 cfg: Config{}, 127 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 128 _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) 129 assert.NoError(t, err) 130 }, 131 wantReachable: false, 132 wantRPC: false, 133 wantWS: true, 134 }, 135 { 136 name: "ws stopped through API", 137 cfg: Config{WSHost: "127.0.0.1"}, 138 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 139 _, err := api.StopWS() 140 assert.NoError(t, err) 141 }, 142 wantReachable: false, 143 wantRPC: false, 144 wantWS: false, 145 }, 146 { 147 name: "ws enabled after RPC", 148 cfg: Config{HTTPHost: "127.0.0.1"}, 149 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 150 _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) 151 assert.NoError(t, err) 152 }, 153 wantReachable: true, 154 wantRPC: true, 155 wantWS: true, 156 }, 157 { 158 name: "ws enabled after RPC then stopped", 159 cfg: Config{HTTPHost: "127.0.0.1"}, 160 fn: func(t *testing.T, n *Node, api *PrivateAdminAPI) { 161 _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) 162 assert.NoError(t, err) 163 164 _, err = api.StopWS() 165 assert.NoError(t, err) 166 }, 167 wantReachable: true, 168 wantRPC: true, 169 wantWS: false, 170 }, 171 } 172 173 for _, test := range tests { 174 test := test 175 t.Run(test.name, func(t *testing.T) { 176 t.Parallel() 177 178 runTestWithServerType(t, test, "http") 179 }) 180 } 181 } 182 183 func runTestWithServerType(t *testing.T, test test, httpServerType string) { 184 // Setting test node config 185 config := test.cfg 186 config.P2P.NoDiscovery = true 187 188 // Create Node. 189 stack, err := New(&config) 190 if err != nil { 191 t.Fatal("can't create node:", err) 192 } 193 194 // Register the test config. 195 stack.config.HTTPServerType = httpServerType 196 stack.config.HTTPPort = 0 197 stack.config.WSPort = 0 198 199 if err := stack.Start(); err != nil { 200 t.Fatal("can't start node:", err) 201 } 202 203 defer stack.Stop() 204 205 // Run the API call hook. 206 if test.fn != nil { 207 test.fn(t, stack, &PrivateAdminAPI{stack}) 208 } 209 210 httpBaseURL := "http://" + stack.httpEndpoint 211 if stack.httpListener != nil { 212 httpBaseURL = "http://" + stack.httpListener.Addr().String() 213 } 214 215 wsBaseURL := "ws://" + stack.wsEndpoint 216 if stack.wsListener != nil { 217 wsBaseURL = "ws://" + stack.wsListener.Addr().String() 218 } 219 220 reachable := checkReachable(httpBaseURL) 221 rpcAvailable := checkRPC(httpBaseURL) 222 wsAvailable := checkRPC(wsBaseURL) 223 224 if reachable != test.wantReachable { 225 t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) 226 } 227 if rpcAvailable != test.wantRPC { 228 t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC)) 229 } 230 if wsAvailable != test.wantWS { 231 t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS)) 232 } 233 } 234 235 // checkReachable checks if the TCP endpoint in rawurl is open. 236 func checkReachable(rawurl string) bool { 237 u, err := url.Parse(rawurl) 238 if err != nil { 239 panic(err) 240 } 241 conn, err := net.Dial("tcp", u.Host) 242 if err != nil { 243 return false 244 } 245 conn.Close() 246 return true 247 } 248 249 // checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK". 250 func checkBodyOK(url string) bool { 251 resp, err := http.Get(url) 252 if err != nil { 253 return false 254 } 255 defer resp.Body.Close() 256 257 if resp.StatusCode != 200 { 258 return false 259 } 260 buf := make([]byte, 2) 261 if _, err = io.ReadFull(resp.Body, buf); err != nil { 262 return false 263 } 264 return bytes.Equal(buf, []byte("OK")) 265 } 266 267 // checkRPC checks whether JSON-RPC works against the given URL. 268 func checkRPC(url string) bool { 269 c, err := rpc.Dial(url) 270 if err != nil { 271 return false 272 } 273 defer c.Close() 274 275 _, err = c.SupportedModules() 276 return err == nil 277 } 278 279 // string/int pointer helpers. 280 func sp(s string) *string { return &s } 281 func ip(i int) *int { return &i } 282 283 func not(ok bool) string { 284 if ok { 285 return "" 286 } 287 return "not " 288 }