api_test.go 9.06 KB
Newer Older
1
// Copyright 2018 The go-ethereum Authors
2
// This file is part of the go-ethereum library.
3
//
4 5
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
6 7 8
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
9
// The go-ethereum library is distributed in the hope that it will be useful,
10 11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
// GNU Lesser General Public License for more details.
13
//
14 15 16
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

17
package core_test
18 19 20 21 22 23 24 25 26 27 28 29

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path/filepath"
	"testing"
	"time"

30
	"github.com/ethereum/go-ethereum/accounts"
31 32 33 34 35 36
	"github.com/ethereum/go-ethereum/accounts/keystore"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/internal/ethapi"
	"github.com/ethereum/go-ethereum/rlp"
37
	"github.com/ethereum/go-ethereum/signer/core"
38
	"github.com/ethereum/go-ethereum/signer/core/apitypes"
39
	"github.com/ethereum/go-ethereum/signer/fourbyte"
40
	"github.com/ethereum/go-ethereum/signer/storage"
41 42 43
)

//Used for testing
44 45 46
type headlessUi struct {
	approveCh chan string // to send approve/deny
	inputCh   chan string // to send password
47 48
}

49
func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
50
	input := <-ui.inputCh
51
	return core.UserInputResponse{Text: input}, nil
52 53
}

54 55
func (ui *headlessUi) OnSignerStartup(info core.StartupInfo)        {}
func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI)       {}
56
func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
57

58
func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
59

60
	switch <-ui.approveCh {
61
	case "Y":
62
		return core.SignTxResponse{request.Transaction, true}, nil
63 64
	case "M": // modify
		// The headless UI always modifies the transaction
65 66 67
		old := big.Int(request.Transaction.Value)
		newVal := big.NewInt(0).Add(&old, big.NewInt(1))
		request.Transaction.Value = hexutil.Big(*newVal)
68
		return core.SignTxResponse{request.Transaction, true}, nil
69
	default:
70
		return core.SignTxResponse{request.Transaction, false}, nil
71 72
	}
}
73

74
func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
75
	approved := (<-ui.approveCh == "Y")
76
	return core.SignDataResponse{approved}, nil
77 78
}

79
func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
80 81 82
	approval := <-ui.approveCh
	//fmt.Printf("approval %s\n", approval)
	switch approval {
83
	case "A":
84
		return core.ListResponse{request.Accounts}, nil
85
	case "1":
86
		l := make([]accounts.Account, 1)
87
		l[0] = request.Accounts[1]
88
		return core.ListResponse{l}, nil
89
	default:
90
		return core.ListResponse{nil}, nil
91 92 93
	}
}

94
func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
95
	if <-ui.approveCh == "Y" {
96
		return core.NewAccountResponse{true}, nil
97
	}
98
	return core.NewAccountResponse{false}, nil
99
}
100

101
func (ui *headlessUi) ShowError(message string) {
102
	//stdout is used by communication
103
	fmt.Fprintln(os.Stderr, message)
104
}
105

106
func (ui *headlessUi) ShowInfo(message string) {
107
	//stdout is used by communication
108
	fmt.Fprintln(os.Stderr, message)
109 110 111 112 113 114 115 116 117 118 119 120 121 122
}

func tmpDirName(t *testing.T) string {
	d, err := ioutil.TempDir("", "eth-keystore-test")
	if err != nil {
		t.Fatal(err)
	}
	d, err = filepath.EvalSymlinks(d)
	if err != nil {
		t.Fatal(err)
	}
	return d
}

123 124
func setup(t *testing.T) (*core.SignerAPI, *headlessUi) {
	db, err := fourbyte.New()
125
	if err != nil {
126
		t.Fatal(err.Error())
127
	}
128
	ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
129
	am := core.StartClefAccountManager(tmpDirName(t), true, true, "")
130
	api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
131
	return api, ui
132

133
}
134
func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
135 136
	ui.approveCh <- "Y"
	ui.inputCh <- "a_long_password"
137 138 139 140 141 142 143
	_, err := api.New(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	// Some time to allow changes to propagate
	time.Sleep(250 * time.Millisecond)
}
144

145
func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) {
146

147 148 149 150 151
	ui.approveCh <- "Y"
	// We will be asked three times to provide a suitable password
	ui.inputCh <- password
	ui.inputCh <- password
	ui.inputCh <- password
152

153
	addr, err := api.New(context.Background())
154 155 156
	if err == nil {
		t.Fatal("Should have returned an error")
	}
157
	if addr != (common.Address{}) {
158 159 160 161
		t.Fatal("Empty address should be returned")
	}
}

162
func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
163
	ui.approveCh <- "N"
164
	addr, err := api.New(context.Background())
165
	if err != core.ErrRequestDenied {
166 167
		t.Fatal(err)
	}
168
	if addr != (common.Address{}) {
169 170 171
		t.Fatal("Empty address should be returned")
	}
}
172

173
func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) {
174 175 176
	ui.approveCh <- "A"
	return api.List(context.Background())

177 178 179 180 181
}

func TestNewAcc(t *testing.T) {
	api, control := setup(t)
	verifyNum := func(num int) {
182 183 184 185 186
		list, err := list(control, api, t)
		if err != nil {
			t.Errorf("Unexpected error %v", err)
		}
		if len(list) != num {
187 188 189 190 191 192 193 194 195 196 197 198
			t.Errorf("Expected %d accounts, got %d", num, len(list))
		}
	}
	// Testing create and create-deny
	createAccount(control, api, t)
	createAccount(control, api, t)
	failCreateAccount(control, api, t)
	failCreateAccount(control, api, t)
	createAccount(control, api, t)
	failCreateAccount(control, api, t)
	createAccount(control, api, t)
	failCreateAccount(control, api, t)
199 200 201 202 203
	verifyNum(4)

	// Fail to create this, due to bad password
	failCreateAccountWithPassword(control, api, "short", t)
	failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
204 205 206 207
	verifyNum(4)

	// Testing listing:
	// Listing one Account
208
	control.approveCh <- "1"
209 210 211 212 213 214 215 216
	list, err := api.List(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	if len(list) != 1 {
		t.Fatalf("List should only show one Account")
	}
	// Listing denied
217
	control.approveCh <- "Nope"
218 219 220 221
	list, err = api.List(context.Background())
	if len(list) != 0 {
		t.Fatalf("List should be empty")
	}
222
	if err != core.ErrRequestDenied {
223 224 225 226
		t.Fatal("Expected deny")
	}
}

227
func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs {
228 229 230 231 232 233
	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
	gas := hexutil.Uint64(21000)
	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
	value := (hexutil.Big)(*big.NewInt(1e18))
	nonce := (hexutil.Uint64)(0)
	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
234
	tx := apitypes.SendTxArgs{
235 236 237
		From:     from,
		To:       &to,
		Gas:      gas,
238
		GasPrice: &gasPrice,
239 240 241 242 243 244 245 246
		Value:    value,
		Data:     &data,
		Nonce:    nonce}
	return tx
}

func TestSignTx(t *testing.T) {
	var (
247
		list      []common.Address
248 249 250 251 252 253
		res, res2 *ethapi.SignTransactionResult
		err       error
	)

	api, control := setup(t)
	createAccount(control, api, t)
254
	control.approveCh <- "A"
255 256 257 258
	list, err = api.List(context.Background())
	if err != nil {
		t.Fatal(err)
	}
259
	a := common.NewMixedcaseAddress(list[0])
260 261 262 263

	methodSig := "test(uint)"
	tx := mkTestTx(a)

264 265
	control.approveCh <- "Y"
	control.inputCh <- "wrongpassword"
266 267 268 269 270 271 272
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
	if res != nil {
		t.Errorf("Expected nil-response, got %v", res)
	}
	if err != keystore.ErrDecrypt {
		t.Errorf("Expected ErrLocked! %v", err)
	}
273
	control.approveCh <- "No way"
274 275 276 277
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
	if res != nil {
		t.Errorf("Expected nil-response, got %v", res)
	}
278
	if err != core.ErrRequestDenied {
279 280
		t.Errorf("Expected ErrRequestDenied! %v", err)
	}
281 282 283
	// Sign with correct password
	control.approveCh <- "Y"
	control.inputCh <- "a_long_password"
284 285 286 287 288 289 290
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)

	if err != nil {
		t.Fatal(err)
	}
	parsedTx := &types.Transaction{}
	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
291

292 293 294 295
	//The tx should NOT be modified by the UI
	if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
		t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
	}
296 297
	control.approveCh <- "Y"
	control.inputCh <- "a_long_password"
298 299 300 301 302 303 304 305 306 307

	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(res.Raw, res2.Raw) {
		t.Error("Expected tx to be unmodified by UI")
	}

	//The tx is modified by the UI
308 309
	control.approveCh <- "M"
	control.inputCh <- "a_long_password"
310 311 312 313 314 315 316

	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
	if err != nil {
		t.Fatal(err)
	}
	parsedTx2 := &types.Transaction{}
	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
317

318 319 320 321 322 323 324 325 326
	//The tx should be modified by the UI
	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
	}
	if bytes.Equal(res.Raw, res2.Raw) {
		t.Error("Expected tx to be modified by UI")
	}

}