js_test.go 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// 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
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
15
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
16

17 18 19 20
package main

import (
	"fmt"
21
	"io/ioutil"
22
	"math/big"
23
	"os"
24
	"path/filepath"
25 26 27
	"regexp"
	"runtime"
	"strconv"
28
	"testing"
29
	"time"
30 31

	"github.com/ethereum/go-ethereum/accounts"
32 33
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/compiler"
zelig's avatar
zelig committed
34
	"github.com/ethereum/go-ethereum/common/httpclient"
35
	"github.com/ethereum/go-ethereum/common/natspec"
36
	"github.com/ethereum/go-ethereum/common/registrar"
37
	"github.com/ethereum/go-ethereum/core"
38 39
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/eth"
40
	"github.com/ethereum/go-ethereum/ethdb"
Bas van Kervel's avatar
Bas van Kervel committed
41
	"github.com/ethereum/go-ethereum/rpc/codec"
42
	"github.com/ethereum/go-ethereum/rpc/comms"
43 44
)

45 46
const (
	testSolcPath = ""
zelig's avatar
zelig committed
47
	solcVersion  = "0.9.23"
48 49 50 51

	testKey     = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
	testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
	testBalance = "10000000000000000000"
Daniel A. Nagy's avatar
Daniel A. Nagy committed
52 53
	// of empty string
	testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
54 55 56
)

var (
zelig's avatar
zelig committed
57
	versionRE   = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
58
	testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
59 60 61 62 63 64
	testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
)

type testjethre struct {
	*jsre
	lastConfirm string
zelig's avatar
zelig committed
65
	client      *httpclient.HTTPClient
66
}
67

68
func (self *testjethre) UnlockAccount(acc []byte) bool {
69
	err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
70 71 72 73 74 75 76 77
	if err != nil {
		panic("unable to unlock")
	}
	return true
}

func (self *testjethre) ConfirmTransaction(tx string) bool {
	if self.ethereum.NatSpec {
zelig's avatar
zelig committed
78
		self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
79 80 81 82 83
	}
	return true
}

func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
84 85 86 87
	return testREPL(t, nil)
}

func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth.Ethereum) {
88
	tmp, err := ioutil.TempDir("", "geth-test")
89
	if err != nil {
90
		t.Fatal(err)
91 92
	}

93
	db, _ := ethdb.NewMemDatabase()
94

95
	core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
96
	ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
97
	am := accounts.NewManager(ks)
98
	conf := &eth.Config{
99
		NodeKey:        testNodeKey,
100
		DataDir:        tmp,
101
		AccountManager: am,
102
		MaxPeers:       0,
103
		Name:           "test",
zelig's avatar
zelig committed
104
		DocRoot:        "/",
105
		SolcPath:       testSolcPath,
106
		PowTest:        true,
107
		NewDB:          func(path string) (ethdb.Database, error) { return db, nil },
108 109 110 111 112
	}
	if config != nil {
		config(conf)
	}
	ethereum, err := eth.New(conf)
113
	if err != nil {
114
		t.Fatal("%v", err)
115
	}
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

	keyb, err := crypto.HexToECDSA(testKey)
	if err != nil {
		t.Fatal(err)
	}
	key := crypto.NewKeyFromECDSA(keyb)
	err = ks.StoreKey(key, "")
	if err != nil {
		t.Fatal(err)
	}

	err = am.Unlock(key.Address, "")
	if err != nil {
		t.Fatal(err)
	}

132
	assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
Bas van Kervel's avatar
Bas van Kervel committed
133
	client := comms.NewInProcClient(codec.JSON)
zelig's avatar
zelig committed
134
	tf := &testjethre{client: ethereum.HTTPClient()}
Bas van Kervel's avatar
Bas van Kervel committed
135
	repl := newJSRE(ethereum, assetPath, "", client, false, tf)
136 137
	tf.jsre = repl
	return tmp, tf, ethereum
138 139 140
}

func TestNodeInfo(t *testing.T) {
141
	t.Skip("broken after p2p update")
142
	tmp, repl, ethereum := testJEthRE(t)
143 144
	if err := ethereum.Start(); err != nil {
		t.Fatalf("error starting ethereum: %v", err)
145 146
	}
	defer ethereum.Stop()
147
	defer os.RemoveAll(tmp)
zelig's avatar
zelig committed
148

149
	want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
Bas van Kervel's avatar
Bas van Kervel committed
150
	checkEvalJSON(t, repl, `admin.nodeInfo`, want)
151 152 153
}

func TestAccounts(t *testing.T) {
154
	tmp, repl, ethereum := testJEthRE(t)
155 156
	if err := ethereum.Start(); err != nil {
		t.Fatalf("error starting ethereum: %v", err)
157 158
	}
	defer ethereum.Stop()
159
	defer os.RemoveAll(tmp)
160

161
	checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
162
	checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`)
Bas van Kervel's avatar
Bas van Kervel committed
163
	val, err := repl.re.Run(`personal.newAccount("password")`)
164 165 166
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}
167 168 169
	addr := val.String()
	if !regexp.MustCompile(`0x[0-9a-f]{40}`).MatchString(addr) {
		t.Errorf("address not hex: %q", addr)
170 171
	}

172
	checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`)
173

174 175 176
}

func TestBlockChain(t *testing.T) {
177
	tmp, repl, ethereum := testJEthRE(t)
178 179
	if err := ethereum.Start(); err != nil {
		t.Fatalf("error starting ethereum: %v", err)
180 181
	}
	defer ethereum.Stop()
182
	defer os.RemoveAll(tmp)
183
	// get current block dump before export/import.
Bas van Kervel's avatar
Bas van Kervel committed
184
	val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))")
185 186 187
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}
188
	beforeExport := val.String()
189

190
	// do the export
191
	extmp, err := ioutil.TempDir("", "geth-test-export")
192
	if err != nil {
193
		t.Fatal(err)
194
	}
195 196
	defer os.RemoveAll(extmp)
	tmpfile := filepath.Join(extmp, "export.chain")
197
	tmpfileq := strconv.Quote(tmpfile)
198

199
	ethereum.BlockChain().Reset()
200

Bas van Kervel's avatar
Bas van Kervel committed
201
	checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
202 203
	if _, err := os.Stat(tmpfile); err != nil {
		t.Fatal(err)
204 205
	}

206
	// check import, verify that dumpBlock gives the same result.
Bas van Kervel's avatar
Bas van Kervel committed
207 208
	checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`)
	checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport)
209 210 211
}

func TestMining(t *testing.T) {
212
	tmp, repl, ethereum := testJEthRE(t)
213 214
	if err := ethereum.Start(); err != nil {
		t.Fatalf("error starting ethereum: %v", err)
215 216
	}
	defer ethereum.Stop()
217
	defer os.RemoveAll(tmp)
218
	checkEvalJSON(t, repl, `eth.mining`, `false`)
219 220 221
}

func TestRPC(t *testing.T) {
222
	tmp, repl, ethereum := testJEthRE(t)
223
	if err := ethereum.Start(); err != nil {
224 225 226 227
		t.Errorf("error starting ethereum: %v", err)
		return
	}
	defer ethereum.Stop()
228
	defer os.RemoveAll(tmp)
229

230
	checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`)
231 232
}

233
func TestCheckTestAccountBalance(t *testing.T) {
234 235 236
	t.Skip() // i don't think it tests the correct behaviour here. it's actually testing
	// internals which shouldn't be tested. This now fails because of a change in the core
	// and i have no means to fix this, sorry - @obscuren
237 238 239 240 241 242 243 244 245 246 247 248
	tmp, repl, ethereum := testJEthRE(t)
	if err := ethereum.Start(); err != nil {
		t.Errorf("error starting ethereum: %v", err)
		return
	}
	defer ethereum.Stop()
	defer os.RemoveAll(tmp)

	repl.re.Run(`primary = "` + testAddress + `"`)
	checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`)
}

Daniel A. Nagy's avatar
Daniel A. Nagy committed
249 250 251 252 253 254 255 256 257
func TestSignature(t *testing.T) {
	tmp, repl, ethereum := testJEthRE(t)
	if err := ethereum.Start(); err != nil {
		t.Errorf("error starting ethereum: %v", err)
		return
	}
	defer ethereum.Stop()
	defer os.RemoveAll(tmp)

258
	val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`)
Daniel A. Nagy's avatar
Daniel A. Nagy committed
259 260 261

	// This is a very preliminary test, lacking actual signature verification
	if err != nil {
262
		t.Errorf("Error running js: %v", err)
Daniel A. Nagy's avatar
Daniel A. Nagy committed
263 264 265 266 267 268 269 270 271 272 273 274
		return
	}
	output := val.String()
	t.Logf("Output: %v", output)

	regex := regexp.MustCompile(`^0x[0-9a-f]{130}$`)
	if !regex.MatchString(output) {
		t.Errorf("Signature is not 65 bytes represented in hexadecimal.")
		return
	}
}

275
func TestContract(t *testing.T) {
zelig's avatar
zelig committed
276
	t.Skip("contract testing is implemented with mining in ethash test mode. This takes about 7seconds to run. Unskip and run on demand")
277 278
	coinbase := common.HexToAddress(testAddress)
	tmp, repl, ethereum := testREPL(t, func(conf *eth.Config) {
Jeffrey Wilcke's avatar
Jeffrey Wilcke committed
279
		conf.Etherbase = coinbase
280 281
		conf.PowTest = true
	})
282 283 284 285 286 287 288
	if err := ethereum.Start(); err != nil {
		t.Errorf("error starting ethereum: %v", err)
		return
	}
	defer ethereum.Stop()
	defer os.RemoveAll(tmp)

289
	reg := registrar.New(repl.xeth)
290
	_, err := reg.SetGlobalRegistrar("", coinbase)
291 292 293
	if err != nil {
		t.Errorf("error setting HashReg: %v", err)
	}
294
	_, err = reg.SetHashReg("", coinbase)
295 296 297
	if err != nil {
		t.Errorf("error setting HashReg: %v", err)
	}
298
	_, err = reg.SetUrlHint("", coinbase)
299 300 301
	if err != nil {
		t.Errorf("error setting HashReg: %v", err)
	}
302 303 304 305 306 307
	/* TODO:
	* lookup receipt and contract addresses by tx hash
	* name registration for HashReg and UrlHint addresses
	* mine those transactions
	* then set once more SetHashReg SetUrlHint
	 */
308 309 310 311 312 313 314 315

	source := `contract test {\n` +
		"   /// @notice Will multiply `a` by 7." + `\n` +
		`   function multiply(uint a) returns(uint d) {\n` +
		`       return a * 7;\n` +
		`   }\n` +
		`}\n`

316
	if checkEvalJSON(t, repl, `admin.stopNatSpec()`, `true`) != nil {
317 318
		return
	}
319 320 321 322 323

	contractInfo, err := ioutil.ReadFile("info_test.json")
	if err != nil {
		t.Fatalf("%v", err)
	}
324 325 326 327 328 329
	if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) != nil {
		return
	}
	if checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) != nil {
		return
	}
330

331 332
	// if solc is found with right version, test it, otherwise read from file
	sol, err := compiler.New("")
333
	if err != nil {
zelig's avatar
zelig committed
334
		t.Logf("solc not found: mocking contract compilation step")
335
	} else if sol.Version() != solcVersion {
zelig's avatar
zelig committed
336
		t.Logf("WARNING: solc different version found (%v, test written for %v, may need to update)", sol.Version(), solcVersion)
337 338 339
	}

	if err != nil {
340 341 342 343 344 345 346 347 348
		info, err := ioutil.ReadFile("info_test.json")
		if err != nil {
			t.Fatalf("%v", err)
		}
		_, err = repl.re.Run(`contract = JSON.parse(` + strconv.Quote(string(info)) + `)`)
		if err != nil {
			t.Errorf("%v", err)
		}
	} else {
349 350 351
		if checkEvalJSON(t, repl, `contract = eth.compile.solidity(source).test`, string(contractInfo)) != nil {
			return
		}
352
	}
353

354 355 356
	if checkEvalJSON(t, repl, `contract.code`, `"0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056"`) != nil {
		return
	}
357

358
	if checkEvalJSON(
359
		t, repl,
360
		`contractaddress = eth.sendTransaction({from: primary, data: contract.code})`,
361
		`"0x46d69d55c3c4b86a924a92c9fc4720bb7bce1d74"`,
362 363 364 365
	) != nil {
		return
	}

366
	if !processTxs(repl, t, 8) {
367 368
		return
	}
369 370 371

	callSetup := `abiDef = JSON.parse('[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]');
Multiply7 = eth.contract(abiDef);
372
multiply7 = Multiply7.at(contractaddress);
373 374 375
`
	_, err = repl.re.Run(callSetup)
	if err != nil {
zelig's avatar
zelig committed
376
		t.Errorf("unexpected error setting up contract, got %v", err)
377
		return
378 379 380 381 382
	}

	expNotice := ""
	if repl.lastConfirm != expNotice {
		t.Errorf("incorrect confirmation message: expected %v, got %v", expNotice, repl.lastConfirm)
383
		return
384 385
	}

386
	if checkEvalJSON(t, repl, `admin.startNatSpec()`, `true`) != nil {
387 388
		return
	}
389
	if checkEvalJSON(t, repl, `multiply7.multiply.sendTransaction(6, { from: primary })`, `"0x4ef9088431a8033e4580d00e4eb2487275e031ff4163c7529df0ef45af17857b"`) != nil {
390 391 392 393 394 395
		return
	}

	if !processTxs(repl, t, 1) {
		return
	}
396

397
	expNotice = `About to submit transaction (no NatSpec info found for contract: content hash not found for '0x87e2802265838c7f14bb69eecd2112911af6767907a702eeaa445239fb20711b'): {"params":[{"to":"0x46d69d55c3c4b86a924a92c9fc4720bb7bce1d74","data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}]}`
398
	if repl.lastConfirm != expNotice {
399 400
		t.Errorf("incorrect confirmation message: expected\n%v, got\n%v", expNotice, repl.lastConfirm)
		return
401 402
	}

403 404
	var contentHash = `"0x86d2b7cf1e72e9a7a3f8d96601f0151742a2f780f1526414304fbe413dc7f9bd"`
	if sol != nil && solcVersion != sol.Version() {
zelig's avatar
zelig committed
405
		modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`))
406 407
		fmt.Printf("modified contractinfo:\n%s\n", modContractInfo)
		contentHash = `"` + common.ToHex(crypto.Sha3([]byte(modContractInfo))) + `"`
zelig's avatar
zelig committed
408
	}
409 410 411
	if checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`) != nil {
		return
	}
412
	if checkEvalJSON(t, repl, `contentHash = admin.saveInfo(contract.info, filename)`, contentHash) != nil {
413 414
		return
	}
415
	if checkEvalJSON(t, repl, `admin.register(primary, contractaddress, contentHash)`, `true`) != nil {
416 417
		return
	}
418
	if checkEvalJSON(t, repl, `admin.registerUrl(primary, contentHash, "file://"+filename)`, `true`) != nil {
419
		return
420 421
	}

422
	if checkEvalJSON(t, repl, `admin.startNatSpec()`, `true`) != nil {
423 424 425 426 427 428 429
		return
	}

	if !processTxs(repl, t, 3) {
		return
	}

430
	if checkEvalJSON(t, repl, `multiply7.multiply.sendTransaction(6, { from: primary })`, `"0x66d7635c12ad0b231e66da2f987ca3dfdca58ffe49c6442aa55960858103fd0c"`) != nil {
431 432
		return
	}
433

434 435 436
	if !processTxs(repl, t, 1) {
		return
	}
437 438 439

	expNotice = "Will multiply 6 by 7."
	if repl.lastConfirm != expNotice {
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
		t.Errorf("incorrect confirmation message: expected\n%v, got\n%v", expNotice, repl.lastConfirm)
		return
	}
}

func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) {
	txs := repl.ethereum.TxPool().GetTransactions()
	return int64(len(txs)), nil
}

func processTxs(repl *testjethre, t *testing.T, expTxc int) bool {
	var txc int64
	var err error
	for i := 0; i < 50; i++ {
		txc, err = pendingTransactions(repl, t)
		if err != nil {
			t.Errorf("unexpected error checking pending transactions: %v", err)
			return false
		}
		if expTxc < int(txc) {
			t.Errorf("too many pending transactions: expected %v, got %v", expTxc, txc)
			return false
		} else if expTxc == int(txc) {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}
	if int(txc) != expTxc {
		t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc)
		return false
470
	}
471
	err = repl.ethereum.StartMining(runtime.NumCPU(), "")
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
	if err != nil {
		t.Errorf("unexpected error mining: %v", err)
		return false
	}
	defer repl.ethereum.StopMining()

	timer := time.NewTimer(100 * time.Second)
	height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1))
	repl.wait <- height
	select {
	case <-timer.C:
		// if times out make sure the xeth loop does not block
		go func() {
			select {
			case repl.wait <- nil:
			case <-repl.wait:
			}
		}()
	case <-repl.wait:
	}
	txc, err = pendingTransactions(repl, t)
	if err != nil {
		t.Errorf("unexpected error checking pending transactions: %v", err)
		return false
	}
	if txc != 0 {
		t.Errorf("%d trasactions were not mined", txc)
		return false
	}
	return true
502 503 504
}

func checkEvalJSON(t *testing.T, re *testjethre, expr, want string) error {
505
	val, err := re.re.Run("JSON.stringify(" + expr + ")")
506 507
	if err == nil && val.String() != want {
		err = fmt.Errorf("Output mismatch for `%s`:\ngot:  %s\nwant: %s", expr, val.String(), want)
508
	}
509 510
	if err != nil {
		_, file, line, _ := runtime.Caller(1)
511
		file = filepath.Base(file)
512 513
		fmt.Printf("\t%s:%d: %v\n", file, line, err)
		t.Fail()
514
	}
515
	return err
516
}