github.com/AlohaMobile/go-ethereum@v1.9.7/signer/core/api_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser 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 // The go-ethereum library 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 Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core_test 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "io/ioutil" 24 "math/big" 25 "os" 26 "path/filepath" 27 "testing" 28 "time" 29 30 "github.com/ethereum/go-ethereum/accounts" 31 "github.com/ethereum/go-ethereum/accounts/keystore" 32 "github.com/ethereum/go-ethereum/common" 33 "github.com/ethereum/go-ethereum/common/hexutil" 34 "github.com/ethereum/go-ethereum/core/types" 35 "github.com/ethereum/go-ethereum/internal/ethapi" 36 "github.com/ethereum/go-ethereum/rlp" 37 "github.com/ethereum/go-ethereum/signer/core" 38 "github.com/ethereum/go-ethereum/signer/fourbyte" 39 "github.com/ethereum/go-ethereum/signer/storage" 40 ) 41 42 //Used for testing 43 type headlessUi struct { 44 approveCh chan string // to send approve/deny 45 inputCh chan string // to send password 46 } 47 48 func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) { 49 input := <-ui.inputCh 50 return core.UserInputResponse{Text: input}, nil 51 } 52 53 func (ui *headlessUi) OnSignerStartup(info core.StartupInfo) {} 54 func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI) {} 55 func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {} 56 57 func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 58 59 switch <-ui.approveCh { 60 case "Y": 61 return core.SignTxResponse{request.Transaction, true}, nil 62 case "M": // modify 63 // The headless UI always modifies the transaction 64 old := big.Int(request.Transaction.Value) 65 newVal := big.NewInt(0).Add(&old, big.NewInt(1)) 66 request.Transaction.Value = hexutil.Big(*newVal) 67 return core.SignTxResponse{request.Transaction, true}, nil 68 default: 69 return core.SignTxResponse{request.Transaction, false}, nil 70 } 71 } 72 73 func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 74 approved := "Y" == <-ui.approveCh 75 return core.SignDataResponse{approved}, nil 76 } 77 78 func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 79 approval := <-ui.approveCh 80 //fmt.Printf("approval %s\n", approval) 81 switch approval { 82 case "A": 83 return core.ListResponse{request.Accounts}, nil 84 case "1": 85 l := make([]accounts.Account, 1) 86 l[0] = request.Accounts[1] 87 return core.ListResponse{l}, nil 88 default: 89 return core.ListResponse{nil}, nil 90 } 91 } 92 93 func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 94 if "Y" == <-ui.approveCh { 95 return core.NewAccountResponse{true}, nil 96 } 97 return core.NewAccountResponse{false}, nil 98 } 99 100 func (ui *headlessUi) ShowError(message string) { 101 //stdout is used by communication 102 fmt.Fprintln(os.Stderr, message) 103 } 104 105 func (ui *headlessUi) ShowInfo(message string) { 106 //stdout is used by communication 107 fmt.Fprintln(os.Stderr, message) 108 } 109 110 func tmpDirName(t *testing.T) string { 111 d, err := ioutil.TempDir("", "eth-keystore-test") 112 if err != nil { 113 t.Fatal(err) 114 } 115 d, err = filepath.EvalSymlinks(d) 116 if err != nil { 117 t.Fatal(err) 118 } 119 return d 120 } 121 122 func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { 123 db, err := fourbyte.New() 124 if err != nil { 125 t.Fatal(err.Error()) 126 } 127 ui := &headlessUi{make(chan string, 20), make(chan string, 20)} 128 am := core.StartClefAccountManager(tmpDirName(t), true, true, "") 129 api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{}) 130 return api, ui 131 132 } 133 func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { 134 ui.approveCh <- "Y" 135 ui.inputCh <- "a_long_password" 136 _, err := api.New(context.Background()) 137 if err != nil { 138 t.Fatal(err) 139 } 140 // Some time to allow changes to propagate 141 time.Sleep(250 * time.Millisecond) 142 } 143 144 func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) { 145 146 ui.approveCh <- "Y" 147 // We will be asked three times to provide a suitable password 148 ui.inputCh <- password 149 ui.inputCh <- password 150 ui.inputCh <- password 151 152 addr, err := api.New(context.Background()) 153 if err == nil { 154 t.Fatal("Should have returned an error") 155 } 156 if addr != (common.Address{}) { 157 t.Fatal("Empty address should be returned") 158 } 159 } 160 161 func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { 162 ui.approveCh <- "N" 163 addr, err := api.New(context.Background()) 164 if err != core.ErrRequestDenied { 165 t.Fatal(err) 166 } 167 if addr != (common.Address{}) { 168 t.Fatal("Empty address should be returned") 169 } 170 } 171 172 func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) { 173 ui.approveCh <- "A" 174 return api.List(context.Background()) 175 176 } 177 178 func TestNewAcc(t *testing.T) { 179 api, control := setup(t) 180 verifyNum := func(num int) { 181 list, err := list(control, api, t) 182 if err != nil { 183 t.Errorf("Unexpected error %v", err) 184 } 185 if len(list) != num { 186 t.Errorf("Expected %d accounts, got %d", num, len(list)) 187 } 188 } 189 // Testing create and create-deny 190 createAccount(control, api, t) 191 createAccount(control, api, t) 192 failCreateAccount(control, api, t) 193 failCreateAccount(control, api, t) 194 createAccount(control, api, t) 195 failCreateAccount(control, api, t) 196 createAccount(control, api, t) 197 failCreateAccount(control, api, t) 198 verifyNum(4) 199 200 // Fail to create this, due to bad password 201 failCreateAccountWithPassword(control, api, "short", t) 202 failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t) 203 verifyNum(4) 204 205 // Testing listing: 206 // Listing one Account 207 control.approveCh <- "1" 208 list, err := api.List(context.Background()) 209 if err != nil { 210 t.Fatal(err) 211 } 212 if len(list) != 1 { 213 t.Fatalf("List should only show one Account") 214 } 215 // Listing denied 216 control.approveCh <- "Nope" 217 list, err = api.List(context.Background()) 218 if len(list) != 0 { 219 t.Fatalf("List should be empty") 220 } 221 if err != core.ErrRequestDenied { 222 t.Fatal("Expected deny") 223 } 224 } 225 226 func mkTestTx(from common.MixedcaseAddress) core.SendTxArgs { 227 to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) 228 gas := hexutil.Uint64(21000) 229 gasPrice := (hexutil.Big)(*big.NewInt(2000000000)) 230 value := (hexutil.Big)(*big.NewInt(1e18)) 231 nonce := (hexutil.Uint64)(0) 232 data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) 233 tx := core.SendTxArgs{ 234 From: from, 235 To: &to, 236 Gas: gas, 237 GasPrice: gasPrice, 238 Value: value, 239 Data: &data, 240 Nonce: nonce} 241 return tx 242 } 243 244 func TestSignTx(t *testing.T) { 245 var ( 246 list []common.Address 247 res, res2 *ethapi.SignTransactionResult 248 err error 249 ) 250 251 api, control := setup(t) 252 createAccount(control, api, t) 253 control.approveCh <- "A" 254 list, err = api.List(context.Background()) 255 if err != nil { 256 t.Fatal(err) 257 } 258 a := common.NewMixedcaseAddress(list[0]) 259 260 methodSig := "test(uint)" 261 tx := mkTestTx(a) 262 263 control.approveCh <- "Y" 264 control.inputCh <- "wrongpassword" 265 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 266 if res != nil { 267 t.Errorf("Expected nil-response, got %v", res) 268 } 269 if err != keystore.ErrDecrypt { 270 t.Errorf("Expected ErrLocked! %v", err) 271 } 272 control.approveCh <- "No way" 273 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 274 if res != nil { 275 t.Errorf("Expected nil-response, got %v", res) 276 } 277 if err != core.ErrRequestDenied { 278 t.Errorf("Expected ErrRequestDenied! %v", err) 279 } 280 // Sign with correct password 281 control.approveCh <- "Y" 282 control.inputCh <- "a_long_password" 283 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 284 285 if err != nil { 286 t.Fatal(err) 287 } 288 parsedTx := &types.Transaction{} 289 rlp.Decode(bytes.NewReader(res.Raw), parsedTx) 290 291 //The tx should NOT be modified by the UI 292 if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { 293 t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) 294 } 295 control.approveCh <- "Y" 296 control.inputCh <- "a_long_password" 297 298 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 299 if err != nil { 300 t.Fatal(err) 301 } 302 if !bytes.Equal(res.Raw, res2.Raw) { 303 t.Error("Expected tx to be unmodified by UI") 304 } 305 306 //The tx is modified by the UI 307 control.approveCh <- "M" 308 control.inputCh <- "a_long_password" 309 310 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 311 if err != nil { 312 t.Fatal(err) 313 } 314 parsedTx2 := &types.Transaction{} 315 rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) 316 317 //The tx should be modified by the UI 318 if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { 319 t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) 320 } 321 if bytes.Equal(res.Raw, res2.Raw) { 322 t.Error("Expected tx to be modified by UI") 323 } 324 325 }