github.com/theQRL/go-zond@v0.2.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/zondapi" 35 "github.com/theQRL/go-zond/signer/core" 36 "github.com/theQRL/go-zond/signer/core/apitypes" 37 "github.com/theQRL/go-zond/signer/fourbyte" 38 "github.com/theQRL/go-zond/signer/storage" 39 ) 40 41 // Used for testing 42 type headlessUi struct { 43 approveCh chan string // to send approve/deny 44 inputCh chan string // to send password 45 } 46 47 func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) { 48 input := <-ui.inputCh 49 return core.UserInputResponse{Text: input}, nil 50 } 51 52 func (ui *headlessUi) OnSignerStartup(info core.StartupInfo) {} 53 func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI) {} 54 func (ui *headlessUi) OnApprovedTx(tx zondapi.SignTransactionResult) {} 55 56 func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 57 switch <-ui.approveCh { 58 case "Y": 59 return core.SignTxResponse{request.Transaction, true}, nil 60 case "M": // modify 61 // The headless UI always modifies the transaction 62 old := big.Int(request.Transaction.Value) 63 newVal := new(big.Int).Add(&old, big.NewInt(1)) 64 request.Transaction.Value = hexutil.Big(*newVal) 65 return core.SignTxResponse{request.Transaction, true}, nil 66 default: 67 return core.SignTxResponse{request.Transaction, false}, nil 68 } 69 } 70 71 func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 72 approved := (<-ui.approveCh == "Y") 73 return core.SignDataResponse{approved}, nil 74 } 75 76 func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 77 approval := <-ui.approveCh 78 //fmt.Printf("approval %s\n", approval) 79 switch approval { 80 case "A": 81 return core.ListResponse{request.Accounts}, nil 82 case "1": 83 l := make([]accounts.Account, 1) 84 l[0] = request.Accounts[1] 85 return core.ListResponse{l}, nil 86 default: 87 return core.ListResponse{nil}, nil 88 } 89 } 90 91 func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 92 if <-ui.approveCh == "Y" { 93 return core.NewAccountResponse{true}, nil 94 } 95 return core.NewAccountResponse{false}, nil 96 } 97 98 func (ui *headlessUi) ShowError(message string) { 99 //stdout is used by communication 100 fmt.Fprintln(os.Stderr, message) 101 } 102 103 func (ui *headlessUi) ShowInfo(message string) { 104 //stdout is used by communication 105 fmt.Fprintln(os.Stderr, message) 106 } 107 108 func tmpDirName(t *testing.T) string { 109 d := t.TempDir() 110 d, err := filepath.EvalSymlinks(d) 111 if err != nil { 112 t.Fatal(err) 113 } 114 return d 115 } 116 117 func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { 118 db, err := fourbyte.New() 119 if err != nil { 120 t.Fatal(err.Error()) 121 } 122 ui := &headlessUi{make(chan string, 20), make(chan string, 20)} 123 am := core.StartClefAccountManager(tmpDirName(t) /*false,*/, true /*, ""*/) 124 api := core.NewSignerAPI(am, 1337 /*true,*/, ui, db, true, &storage.NoStorage{}) 125 return api, ui 126 } 127 func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { 128 ui.approveCh <- "Y" 129 ui.inputCh <- "a_long_password" 130 _, err := api.New(context.Background()) 131 if err != nil { 132 t.Fatal(err) 133 } 134 // Some time to allow changes to propagate 135 time.Sleep(250 * time.Millisecond) 136 } 137 138 func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) { 139 ui.approveCh <- "Y" 140 // We will be asked three times to provide a suitable password 141 ui.inputCh <- password 142 ui.inputCh <- password 143 ui.inputCh <- password 144 145 addr, err := api.New(context.Background()) 146 if err == nil { 147 t.Fatal("Should have returned an error") 148 } 149 if addr != (common.Address{}) { 150 t.Fatal("Empty address should be returned") 151 } 152 } 153 154 func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { 155 ui.approveCh <- "N" 156 addr, err := api.New(context.Background()) 157 if err != core.ErrRequestDenied { 158 t.Fatal(err) 159 } 160 if addr != (common.Address{}) { 161 t.Fatal("Empty address should be returned") 162 } 163 } 164 165 func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) { 166 ui.approveCh <- "A" 167 return api.List(context.Background()) 168 } 169 170 func TestNewAcc(t *testing.T) { 171 api, control := setup(t) 172 verifyNum := func(num int) { 173 list, err := list(control, api, t) 174 if err != nil { 175 t.Errorf("Unexpected error %v", err) 176 } 177 if len(list) != num { 178 t.Errorf("Expected %d accounts, got %d", num, len(list)) 179 } 180 } 181 // Testing create and create-deny 182 createAccount(control, api, t) 183 createAccount(control, api, t) 184 failCreateAccount(control, api, t) 185 failCreateAccount(control, api, t) 186 createAccount(control, api, t) 187 failCreateAccount(control, api, t) 188 createAccount(control, api, t) 189 failCreateAccount(control, api, t) 190 verifyNum(4) 191 192 // Fail to create this, due to bad password 193 failCreateAccountWithPassword(control, api, "short", t) 194 failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t) 195 verifyNum(4) 196 197 // Testing listing: 198 // Listing one Account 199 control.approveCh <- "1" 200 list, err := api.List(context.Background()) 201 if err != nil { 202 t.Fatal(err) 203 } 204 if len(list) != 1 { 205 t.Fatalf("List should only show one Account") 206 } 207 // Listing denied 208 control.approveCh <- "Nope" 209 list, err = api.List(context.Background()) 210 if len(list) != 0 { 211 t.Fatalf("List should be empty") 212 } 213 if err != core.ErrRequestDenied { 214 t.Fatal("Expected deny") 215 } 216 } 217 218 func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs { 219 address, _ := common.NewAddressFromString("Z0000000000000000000000000000000000001337") 220 to := common.NewMixedcaseAddress(address) 221 gas := hexutil.Uint64(21000) 222 maxFeePerGas := (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 MaxFeePerGas: &maxFeePerGas, 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 *zondapi.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 if err != nil { 281 t.Fatal(err) 282 } 283 284 parsedTx := &types.Transaction{} 285 if err := parsedTx.UnmarshalBinary(res.Raw); err != nil { 286 t.Fatal(err) 287 } 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 if err := parsedTx2.UnmarshalBinary(res.Raw); err != nil { 314 t.Fatal(err) 315 } 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 }