github.com/felberj/go-ethereum@v1.8.23/signer/core/api_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // 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 package core 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "io/ioutil" 25 "math/big" 26 "os" 27 "path/filepath" 28 "testing" 29 "time" 30 31 "github.com/ethereum/go-ethereum/accounts/keystore" 32 "github.com/ethereum/go-ethereum/cmd/utils" 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/common/hexutil" 35 "github.com/ethereum/go-ethereum/core/types" 36 "github.com/ethereum/go-ethereum/internal/ethapi" 37 "github.com/ethereum/go-ethereum/rlp" 38 ) 39 40 //Used for testing 41 type HeadlessUI struct { 42 controller chan string 43 } 44 45 func (ui *HeadlessUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) { 46 return UserInputResponse{}, errors.New("not implemented") 47 } 48 49 func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) { 50 } 51 52 func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) { 53 fmt.Printf("OnApproved()\n") 54 } 55 56 func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { 57 58 switch <-ui.controller { 59 case "Y": 60 return SignTxResponse{request.Transaction, true, <-ui.controller}, nil 61 case "M": //Modify 62 old := big.Int(request.Transaction.Value) 63 newVal := big.NewInt(0).Add(&old, big.NewInt(1)) 64 request.Transaction.Value = hexutil.Big(*newVal) 65 return SignTxResponse{request.Transaction, true, <-ui.controller}, nil 66 default: 67 return SignTxResponse{request.Transaction, false, ""}, nil 68 } 69 } 70 71 func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { 72 if "Y" == <-ui.controller { 73 return SignDataResponse{true, <-ui.controller}, nil 74 } 75 return SignDataResponse{false, ""}, nil 76 } 77 78 func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { 79 return ExportResponse{<-ui.controller == "Y"}, nil 80 81 } 82 83 func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { 84 if "Y" == <-ui.controller { 85 return ImportResponse{true, <-ui.controller, <-ui.controller}, nil 86 } 87 return ImportResponse{false, "", ""}, nil 88 } 89 90 func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) { 91 switch <-ui.controller { 92 case "A": 93 return ListResponse{request.Accounts}, nil 94 case "1": 95 l := make([]Account, 1) 96 l[0] = request.Accounts[1] 97 return ListResponse{l}, nil 98 default: 99 return ListResponse{nil}, nil 100 } 101 } 102 103 func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { 104 if "Y" == <-ui.controller { 105 return NewAccountResponse{true, <-ui.controller}, nil 106 } 107 return NewAccountResponse{false, ""}, nil 108 } 109 110 func (ui *HeadlessUI) ShowError(message string) { 111 //stdout is used by communication 112 fmt.Fprintln(os.Stderr, message) 113 } 114 115 func (ui *HeadlessUI) ShowInfo(message string) { 116 //stdout is used by communication 117 fmt.Fprintln(os.Stderr, message) 118 } 119 120 func tmpDirName(t *testing.T) string { 121 d, err := ioutil.TempDir("", "eth-keystore-test") 122 if err != nil { 123 t.Fatal(err) 124 } 125 d, err = filepath.EvalSymlinks(d) 126 if err != nil { 127 t.Fatal(err) 128 } 129 return d 130 } 131 132 func setup(t *testing.T) (*SignerAPI, chan string) { 133 134 controller := make(chan string, 20) 135 136 db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json") 137 if err != nil { 138 utils.Fatalf(err.Error()) 139 } 140 var ( 141 ui = &HeadlessUI{controller} 142 api = NewSignerAPI( 143 1, 144 tmpDirName(t), 145 true, 146 ui, 147 db, 148 true, true) 149 ) 150 return api, controller 151 } 152 func createAccount(control chan string, api *SignerAPI, t *testing.T) { 153 154 control <- "Y" 155 control <- "a_long_password" 156 _, err := api.New(context.Background()) 157 if err != nil { 158 t.Fatal(err) 159 } 160 // Some time to allow changes to propagate 161 time.Sleep(250 * time.Millisecond) 162 } 163 164 func failCreateAccountWithPassword(control chan string, api *SignerAPI, password string, t *testing.T) { 165 166 control <- "Y" 167 control <- password 168 control <- "Y" 169 control <- password 170 control <- "Y" 171 control <- password 172 173 acc, err := api.New(context.Background()) 174 if err == nil { 175 t.Fatal("Should have returned an error") 176 } 177 if acc.Address != (common.Address{}) { 178 t.Fatal("Empty address should be returned") 179 } 180 } 181 182 func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) { 183 control <- "N" 184 acc, err := api.New(context.Background()) 185 if err != ErrRequestDenied { 186 t.Fatal(err) 187 } 188 if acc.Address != (common.Address{}) { 189 t.Fatal("Empty address should be returned") 190 } 191 } 192 193 func list(control chan string, api *SignerAPI, t *testing.T) []common.Address { 194 control <- "A" 195 list, err := api.List(context.Background()) 196 if err != nil { 197 t.Fatal(err) 198 } 199 return list 200 } 201 202 func TestNewAcc(t *testing.T) { 203 api, control := setup(t) 204 verifyNum := func(num int) { 205 if list := list(control, api, t); len(list) != num { 206 t.Errorf("Expected %d accounts, got %d", num, len(list)) 207 } 208 } 209 // Testing create and create-deny 210 createAccount(control, api, t) 211 createAccount(control, api, t) 212 failCreateAccount(control, api, t) 213 failCreateAccount(control, api, t) 214 createAccount(control, api, t) 215 failCreateAccount(control, api, t) 216 createAccount(control, api, t) 217 failCreateAccount(control, api, t) 218 219 verifyNum(4) 220 221 // Fail to create this, due to bad password 222 failCreateAccountWithPassword(control, api, "short", t) 223 failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t) 224 225 verifyNum(4) 226 227 // Testing listing: 228 // Listing one Account 229 control <- "1" 230 list, err := api.List(context.Background()) 231 if err != nil { 232 t.Fatal(err) 233 } 234 if len(list) != 1 { 235 t.Fatalf("List should only show one Account") 236 } 237 // Listing denied 238 control <- "Nope" 239 list, err = api.List(context.Background()) 240 if len(list) != 0 { 241 t.Fatalf("List should be empty") 242 } 243 if err != ErrRequestDenied { 244 t.Fatal("Expected deny") 245 } 246 } 247 248 func TestSignData(t *testing.T) { 249 api, control := setup(t) 250 //Create two accounts 251 createAccount(control, api, t) 252 createAccount(control, api, t) 253 control <- "1" 254 list, err := api.List(context.Background()) 255 if err != nil { 256 t.Fatal(err) 257 } 258 a := common.NewMixedcaseAddress(list[0]) 259 260 control <- "Y" 261 control <- "wrongpassword" 262 h, err := api.Sign(context.Background(), a, []byte("EHLO world")) 263 if h != nil { 264 t.Errorf("Expected nil-data, got %x", h) 265 } 266 if err != keystore.ErrDecrypt { 267 t.Errorf("Expected ErrLocked! %v", err) 268 } 269 control <- "No way" 270 h, err = api.Sign(context.Background(), a, []byte("EHLO world")) 271 if h != nil { 272 t.Errorf("Expected nil-data, got %x", h) 273 } 274 if err != ErrRequestDenied { 275 t.Errorf("Expected ErrRequestDenied! %v", err) 276 } 277 control <- "Y" 278 control <- "a_long_password" 279 h, err = api.Sign(context.Background(), a, []byte("EHLO world")) 280 if err != nil { 281 t.Fatal(err) 282 } 283 if h == nil || len(h) != 65 { 284 t.Errorf("Expected 65 byte signature (got %d bytes)", len(h)) 285 } 286 } 287 func mkTestTx(from common.MixedcaseAddress) SendTxArgs { 288 to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) 289 gas := hexutil.Uint64(21000) 290 gasPrice := (hexutil.Big)(*big.NewInt(2000000000)) 291 value := (hexutil.Big)(*big.NewInt(1e18)) 292 nonce := (hexutil.Uint64)(0) 293 data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) 294 tx := SendTxArgs{ 295 From: from, 296 To: &to, 297 Gas: gas, 298 GasPrice: gasPrice, 299 Value: value, 300 Data: &data, 301 Nonce: nonce} 302 return tx 303 } 304 305 func TestSignTx(t *testing.T) { 306 var ( 307 list []common.Address 308 res, res2 *ethapi.SignTransactionResult 309 err error 310 ) 311 312 api, control := setup(t) 313 createAccount(control, api, t) 314 control <- "A" 315 list, err = api.List(context.Background()) 316 if err != nil { 317 t.Fatal(err) 318 } 319 a := common.NewMixedcaseAddress(list[0]) 320 321 methodSig := "test(uint)" 322 tx := mkTestTx(a) 323 324 control <- "Y" 325 control <- "wrongpassword" 326 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 327 if res != nil { 328 t.Errorf("Expected nil-response, got %v", res) 329 } 330 if err != keystore.ErrDecrypt { 331 t.Errorf("Expected ErrLocked! %v", err) 332 } 333 control <- "No way" 334 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 335 if res != nil { 336 t.Errorf("Expected nil-response, got %v", res) 337 } 338 if err != ErrRequestDenied { 339 t.Errorf("Expected ErrRequestDenied! %v", err) 340 } 341 control <- "Y" 342 control <- "a_long_password" 343 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 344 345 if err != nil { 346 t.Fatal(err) 347 } 348 parsedTx := &types.Transaction{} 349 rlp.Decode(bytes.NewReader(res.Raw), parsedTx) 350 351 //The tx should NOT be modified by the UI 352 if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { 353 t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) 354 } 355 control <- "Y" 356 control <- "a_long_password" 357 358 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 359 if err != nil { 360 t.Fatal(err) 361 } 362 if !bytes.Equal(res.Raw, res2.Raw) { 363 t.Error("Expected tx to be unmodified by UI") 364 } 365 366 //The tx is modified by the UI 367 control <- "M" 368 control <- "a_long_password" 369 370 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 371 if err != nil { 372 t.Fatal(err) 373 } 374 parsedTx2 := &types.Transaction{} 375 rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) 376 377 //The tx should be modified by the UI 378 if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { 379 t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) 380 } 381 if bytes.Equal(res.Raw, res2.Raw) { 382 t.Error("Expected tx to be modified by UI") 383 } 384 385 } 386 387 /* 388 func TestAsyncronousResponses(t *testing.T){ 389 390 //Set up one account 391 api, control := setup(t) 392 createAccount(control, api, t) 393 394 // Two transactions, the second one with larger value than the first 395 tx1 := mkTestTx() 396 newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1)) 397 tx2 := mkTestTx() 398 tx2.Value = (*hexutil.Big)(newVal) 399 400 control <- "W" //wait 401 control <- "Y" // 402 control <- "a_long_password" 403 control <- "Y" // 404 control <- "a_long_password" 405 406 var err error 407 408 h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil) 409 h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil) 410 411 412 } 413 */