github.com/theQRL/go-zond@v0.1.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/theQRL/go-zond/accounts" 30 "github.com/theQRL/go-zond/accounts/keystore" 31 "github.com/theQRL/go-zond/common" 32 "github.com/theQRL/go-zond/common/hexutil" 33 "github.com/theQRL/go-zond/core/types" 34 "github.com/theQRL/go-zond/internal/ethapi" 35 "github.com/theQRL/go-zond/rlp" 36 "github.com/theQRL/go-zond/signer/core" 37 "github.com/theQRL/go-zond/signer/core/apitypes" 38 "github.com/theQRL/go-zond/signer/fourbyte" 39 "github.com/theQRL/go-zond/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 api, control := setup(t) 173 verifyNum := func(num int) { 174 list, err := list(control, api, t) 175 if err != nil { 176 t.Errorf("Unexpected error %v", err) 177 } 178 if len(list) != num { 179 t.Errorf("Expected %d accounts, got %d", num, len(list)) 180 } 181 } 182 // Testing create and create-deny 183 createAccount(control, api, t) 184 createAccount(control, api, t) 185 failCreateAccount(control, api, t) 186 failCreateAccount(control, api, t) 187 createAccount(control, api, t) 188 failCreateAccount(control, api, t) 189 createAccount(control, api, t) 190 failCreateAccount(control, api, t) 191 verifyNum(4) 192 193 // Fail to create this, due to bad password 194 failCreateAccountWithPassword(control, api, "short", t) 195 failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t) 196 verifyNum(4) 197 198 // Testing listing: 199 // Listing one Account 200 control.approveCh <- "1" 201 list, err := api.List(context.Background()) 202 if err != nil { 203 t.Fatal(err) 204 } 205 if len(list) != 1 { 206 t.Fatalf("List should only show one Account") 207 } 208 // Listing denied 209 control.approveCh <- "Nope" 210 list, err = api.List(context.Background()) 211 if len(list) != 0 { 212 t.Fatalf("List should be empty") 213 } 214 if err != core.ErrRequestDenied { 215 t.Fatal("Expected deny") 216 } 217 } 218 219 func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs { 220 to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) 221 gas := hexutil.Uint64(21000) 222 gasPrice := (hexutil.Big)(*big.NewInt(2000000000)) 223 value := (hexutil.Big)(*big.NewInt(1e18)) 224 nonce := (hexutil.Uint64)(0) 225 data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) 226 tx := apitypes.SendTxArgs{ 227 From: from, 228 To: &to, 229 Gas: gas, 230 GasPrice: &gasPrice, 231 Value: value, 232 Data: &data, 233 Nonce: nonce} 234 return tx 235 } 236 237 func TestSignTx(t *testing.T) { 238 var ( 239 list []common.Address 240 res, res2 *ethapi.SignTransactionResult 241 err error 242 ) 243 244 api, control := setup(t) 245 createAccount(control, api, t) 246 control.approveCh <- "A" 247 list, err = api.List(context.Background()) 248 if err != nil { 249 t.Fatal(err) 250 } 251 if len(list) == 0 { 252 t.Fatal("Unexpected empty list") 253 } 254 a := common.NewMixedcaseAddress(list[0]) 255 256 methodSig := "test(uint)" 257 tx := mkTestTx(a) 258 259 control.approveCh <- "Y" 260 control.inputCh <- "wrongpassword" 261 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 262 if res != nil { 263 t.Errorf("Expected nil-response, got %v", res) 264 } 265 if err != keystore.ErrDecrypt { 266 t.Errorf("Expected ErrLocked! %v", err) 267 } 268 control.approveCh <- "No way" 269 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 270 if res != nil { 271 t.Errorf("Expected nil-response, got %v", res) 272 } 273 if err != core.ErrRequestDenied { 274 t.Errorf("Expected ErrRequestDenied! %v", err) 275 } 276 // Sign with correct password 277 control.approveCh <- "Y" 278 control.inputCh <- "a_long_password" 279 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 280 281 if err != nil { 282 t.Fatal(err) 283 } 284 parsedTx := &types.Transaction{} 285 rlp.DecodeBytes(res.Raw, parsedTx) 286 287 //The tx should NOT be modified by the UI 288 if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { 289 t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) 290 } 291 control.approveCh <- "Y" 292 control.inputCh <- "a_long_password" 293 294 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 295 if err != nil { 296 t.Fatal(err) 297 } 298 if !bytes.Equal(res.Raw, res2.Raw) { 299 t.Error("Expected tx to be unmodified by UI") 300 } 301 302 //The tx is modified by the UI 303 control.approveCh <- "M" 304 control.inputCh <- "a_long_password" 305 306 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 307 if err != nil { 308 t.Fatal(err) 309 } 310 parsedTx2 := &types.Transaction{} 311 rlp.DecodeBytes(res.Raw, parsedTx2) 312 313 //The tx should be modified by the UI 314 if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { 315 t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) 316 } 317 if bytes.Equal(res.Raw, res2.Raw) { 318 t.Error("Expected tx to be modified by UI") 319 } 320 }