aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/gethrpctest/main.go
blob: 636d329e484bfc7f8b3ebdd50b40776804c86d04 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// Copyright 2015 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

// gethrpctest is a command to run the external RPC tests.
package main

import (
    "flag"
    "io/ioutil"
    "log"
    "os"
    "os/signal"
    "path/filepath"
    "runtime"

    "github.com/ethereum/go-ethereum/accounts"
    "github.com/ethereum/go-ethereum/cmd/utils"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/eth"
    "github.com/ethereum/go-ethereum/ethdb"
    "github.com/ethereum/go-ethereum/logger"
    "github.com/ethereum/go-ethereum/logger/glog"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/rpc/api"
    "github.com/ethereum/go-ethereum/rpc/codec"
    "github.com/ethereum/go-ethereum/rpc/comms"
    rpc "github.com/ethereum/go-ethereum/rpc/v2"
    "github.com/ethereum/go-ethereum/tests"
    "github.com/ethereum/go-ethereum/whisper"
    "github.com/ethereum/go-ethereum/xeth"
)

const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"

var (
    testFile = flag.String("json", "", "Path to the .json test file to load")
    testName = flag.String("test", "", "Name of the test from the .json file to run")
    testKey  = flag.String("key", defaultTestKey, "Private key of a test account to inject")
)

func main() {
    flag.Parse()

    // Load the test suite to run the RPC against
    tests, err := tests.LoadBlockTests(*testFile)
    if err != nil {
        log.Fatalf("Failed to load test suite: %v", err)
    }
    test, found := tests[*testName]
    if !found {
        log.Fatalf("Requested test (%s) not found within suite", *testName)
    }
    // Create the protocol stack to run the test with
    keydir, err := ioutil.TempDir("", "")
    if err != nil {
        log.Fatalf("Failed to create temporary keystore directory: %v", err)
    }
    defer os.RemoveAll(keydir)

    stack, err := MakeSystemNode(keydir, *testKey, test)
    if err != nil {
        log.Fatalf("Failed to assemble test stack: %v", err)
    }
    if err := stack.Start(); err != nil {
        log.Fatalf("Failed to start test node: %v", err)
    }
    defer stack.Stop()

    log.Println("Test node started...")

    // Make sure the tests contained within the suite pass
    if err := RunTest(stack, test); err != nil {
        log.Fatalf("Failed to run the pre-configured test: %v", err)
    }
    log.Println("Initial test suite passed...")

    if err := StartIPC(stack); err != nil {
        log.Fatalf("Failed to start IPC interface: %v\n", err)
    }
    log.Println("IPC Interface started, accepting requests...")

    // Start the RPC interface and wait until terminated
    if err := StartRPC(stack); err != nil {
        log.Fatalf("Failed to start RPC interface: %v", err)
    }
    log.Println("RPC Interface started, accepting requests...")

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
}

// MakeSystemNode configures a protocol stack for the RPC tests based on a given
// keystore path and initial pre-state.
func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) {
    // Create a networkless protocol stack
    stack, err := node.New(&node.Config{NoDiscovery: true})
    if err != nil {
        return nil, err
    }
    // Create the keystore and inject an unlocked account if requested
    keystore := crypto.NewKeyStorePassphrase(keydir, crypto.StandardScryptN, crypto.StandardScryptP)
    accman := accounts.NewManager(keystore)

    if len(privkey) > 0 {
        key, err := crypto.HexToECDSA(privkey)
        if err != nil {
            return nil, err
        }
        if err := keystore.StoreKey(crypto.NewKeyFromECDSA(key), ""); err != nil {
            return nil, err
        }
        if err := accman.Unlock(crypto.NewKeyFromECDSA(key).Address, ""); err != nil {
            return nil, err
        }
    }
    // Initialize and register the Ethereum protocol
    db, _ := ethdb.NewMemDatabase()
    if _, err := test.InsertPreState(db, accman); err != nil {
        return nil, err
    }
    ethConf := &eth.Config{
        TestGenesisState: db,
        TestGenesisBlock: test.Genesis,
        AccountManager:   accman,
    }
    if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
        return nil, err
    }
    // Initialize and register the Whisper protocol
    if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil {
        return nil, err
    }
    return stack, nil
}

// RunTest executes the specified test against an already pre-configured protocol
// stack to ensure basic checks pass before running RPC tests.
func RunTest(stack *node.Node, test *tests.BlockTest) error {
    var ethereum *eth.Ethereum
    stack.Service(&ethereum)
    blockchain := ethereum.BlockChain()

    // Process the blocks and verify the imported headers
    blocks, err := test.TryBlocksInsert(blockchain)
    if err != nil {
        return err
    }
    if err := test.ValidateImportedHeaders(blockchain, blocks); err != nil {
        return err
    }
    // Retrieve the assembled state and validate it
    stateDb, err := blockchain.State()
    if err != nil {
        return err
    }
    if err := test.ValidatePostState(stateDb); err != nil {
        return err
    }
    return nil
}

// StartRPC initializes an RPC interface to the given protocol stack.
func StartRPC(stack *node.Node) error {
    config := comms.HttpConfig{
        ListenAddress: "127.0.0.1",
        ListenPort:    8545,
    }
    xeth := xeth.New(stack, nil)
    codec := codec.JSON

    apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack)
    if err != nil {
        return err
    }
    return comms.StartHttp(config, codec, api.Merge(apis...))
}

// StartRPC initializes an IPC interface to the given protocol stack.
func StartIPC(stack *node.Node) error {
    var ethereum *eth.Ethereum
    if err := stack.Service(&ethereum); err != nil {
        return err
    }

    endpoint := `\\.\pipe\geth.ipc`
    if runtime.GOOS != "windows" {
        endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc")
    }

    config := comms.IpcConfig{
        Endpoint: endpoint,
    }

    listener, err := comms.CreateListener(config)
    if err != nil {
        return err
    }

    server := rpc.NewServer()

    // register package API's this node provides
    offered := stack.RPCAPIs()
    for _, api := range offered {
        server.RegisterName(api.Namespace, api.Service)
        glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace)
    }

    web3 := utils.NewPublicWeb3API(stack)
    server.RegisterName("web3", web3)
    net := utils.NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
    server.RegisterName("net", net)

    go func() {
        glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint)
        for {
            conn, err := listener.Accept()
            if err != nil {
                glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err)
            }

            codec := rpc.NewJSONCodec(conn)
            go server.ServeCodec(codec)
        }
    }()

    return nil
}