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