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