github.com/core-coin/go-core/v2@v2.1.9/signer/core/api_test.go (about) 1 // Copyright 2018 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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/core-coin/go-core/v2/internal/xcbapi" 31 32 "github.com/core-coin/go-core/v2/accounts" 33 "github.com/core-coin/go-core/v2/accounts/keystore" 34 "github.com/core-coin/go-core/v2/common" 35 "github.com/core-coin/go-core/v2/common/hexutil" 36 "github.com/core-coin/go-core/v2/core/types" 37 "github.com/core-coin/go-core/v2/rlp" 38 "github.com/core-coin/go-core/v2/signer/core" 39 "github.com/core-coin/go-core/v2/signer/fourbyte" 40 "github.com/core-coin/go-core/v2/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 xcbapi.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 := (<-ui.approveCh == "Y") 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 <-ui.approveCh == "Y" { 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("", "xcb-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) 130 api := core.NewSignerAPI(am, 1, 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.Address) core.SendTxArgs { 228 to, err := common.HexToAddress("cb390000000000000000000000000000000000001337") 229 if err != nil { 230 panic(err) 231 } 232 energy := hexutil.Uint64(21000) 233 energyPrice := (hexutil.Big)(*big.NewInt(2000000000)) 234 value := (hexutil.Big)(*big.NewInt(1e18)) 235 nonce := (hexutil.Uint64)(0) 236 data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) 237 tx := core.SendTxArgs{ 238 From: from, 239 To: &to, 240 Energy: energy, 241 EnergyPrice: energyPrice, 242 Value: value, 243 Data: &data, 244 Nonce: nonce} 245 return tx 246 } 247 248 func TestSignTx(t *testing.T) { 249 var ( 250 list []common.Address 251 res, res2 *xcbapi.SignTransactionResult 252 err error 253 ) 254 255 api, control := setup(t) 256 createAccount(control, api, t) 257 control.approveCh <- "A" 258 list, err = api.List(context.Background()) 259 if err != nil { 260 t.Fatal(err) 261 } 262 a := list[0] 263 264 methodSig := "test(uint)" 265 tx := mkTestTx(a) 266 267 control.approveCh <- "Y" 268 control.inputCh <- "wrongpassword" 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 != keystore.ErrDecrypt { 274 t.Errorf("Expected ErrLocked! %v", err) 275 } 276 control.approveCh <- "No way" 277 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 278 if res != nil { 279 t.Errorf("Expected nil-response, got %v", res) 280 } 281 if err != core.ErrRequestDenied { 282 t.Errorf("Expected ErrRequestDenied! %v", err) 283 } 284 // Sign with correct password 285 control.approveCh <- "Y" 286 control.inputCh <- "a_long_password" 287 res, err = api.SignTransaction(context.Background(), tx, &methodSig) 288 289 if err != nil { 290 t.Fatal(err) 291 } 292 parsedTx := &types.Transaction{} 293 rlp.Decode(bytes.NewReader(res.Raw), parsedTx) 294 295 //The tx should NOT be modified by the UI 296 if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { 297 t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) 298 } 299 control.approveCh <- "Y" 300 control.inputCh <- "a_long_password" 301 302 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 303 if err != nil { 304 t.Fatal(err) 305 } 306 if !bytes.Equal(res.Raw, res2.Raw) { 307 t.Error("Expected tx to be unmodified by UI") 308 } 309 310 //The tx is modified by the UI 311 control.approveCh <- "M" 312 control.inputCh <- "a_long_password" 313 314 res2, err = api.SignTransaction(context.Background(), tx, &methodSig) 315 if err != nil { 316 t.Fatal(err) 317 } 318 parsedTx2 := &types.Transaction{} 319 rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) 320 321 //The tx should be modified by the UI 322 if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { 323 t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) 324 } 325 if bytes.Equal(res.Raw, res2.Raw) { 326 t.Error("Expected tx to be modified by UI") 327 } 328 329 }