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