github.com/daeglee/go-ethereum@v0.0.0-20190504220456-cad3e8d18e9b/signer/core/api_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  //
    17  package core
    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/ethereum/go-ethereum/accounts"
    31  	"github.com/ethereum/go-ethereum/accounts/keystore"
    32  	"github.com/ethereum/go-ethereum/common"
    33  	"github.com/ethereum/go-ethereum/common/hexutil"
    34  	"github.com/ethereum/go-ethereum/core/types"
    35  	"github.com/ethereum/go-ethereum/internal/ethapi"
    36  	"github.com/ethereum/go-ethereum/rlp"
    37  	"github.com/ethereum/go-ethereum/signer/storage"
    38  )
    39  
    40  //Used for testing
    41  type headlessUi struct {
    42  	approveCh chan string // to send approve/deny
    43  	inputCh   chan string // to send password
    44  }
    45  
    46  func (ui *headlessUi) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
    47  	input := <-ui.inputCh
    48  	return UserInputResponse{Text: input}, nil
    49  }
    50  
    51  func (ui *headlessUi) OnSignerStartup(info StartupInfo)             {}
    52  func (ui *headlessUi) RegisterUIServer(api *UIServerAPI)            {}
    53  func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
    54  
    55  func (ui *headlessUi) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
    56  
    57  	switch <-ui.approveCh {
    58  	case "Y":
    59  		return 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 := big.NewInt(0).Add(&old, big.NewInt(1))
    64  		request.Transaction.Value = hexutil.Big(*newVal)
    65  		return SignTxResponse{request.Transaction, true}, nil
    66  	default:
    67  		return SignTxResponse{request.Transaction, false}, nil
    68  	}
    69  }
    70  
    71  func (ui *headlessUi) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
    72  	approved := "Y" == <-ui.approveCh
    73  	return SignDataResponse{approved}, nil
    74  }
    75  
    76  func (ui *headlessUi) ApproveListing(request *ListRequest) (ListResponse, error) {
    77  	approval := <-ui.approveCh
    78  	//fmt.Printf("approval %s\n", approval)
    79  	switch approval {
    80  	case "A":
    81  		return ListResponse{request.Accounts}, nil
    82  	case "1":
    83  		l := make([]accounts.Account, 1)
    84  		l[0] = request.Accounts[1]
    85  		return ListResponse{l}, nil
    86  	default:
    87  		return ListResponse{nil}, nil
    88  	}
    89  }
    90  
    91  func (ui *headlessUi) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
    92  	if "Y" == <-ui.approveCh {
    93  		return NewAccountResponse{true}, nil
    94  	}
    95  	return 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, err := ioutil.TempDir("", "eth-keystore-test")
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	d, err = filepath.EvalSymlinks(d)
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	return d
   118  }
   119  
   120  func setup(t *testing.T) (*SignerAPI, *headlessUi) {
   121  	db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
   122  	if err != nil {
   123  		t.Fatal(err.Error())
   124  	}
   125  	ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
   126  	am := StartClefAccountManager(tmpDirName(t), true, true)
   127  	api := NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
   128  	return api, ui
   129  
   130  }
   131  func createAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
   132  	ui.approveCh <- "Y"
   133  	ui.inputCh <- "a_long_password"
   134  	_, err := api.New(context.Background())
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	// Some time to allow changes to propagate
   139  	time.Sleep(250 * time.Millisecond)
   140  }
   141  
   142  func failCreateAccountWithPassword(ui *headlessUi, api *SignerAPI, password string, t *testing.T) {
   143  
   144  	ui.approveCh <- "Y"
   145  	// We will be asked three times to provide a suitable password
   146  	ui.inputCh <- password
   147  	ui.inputCh <- password
   148  	ui.inputCh <- password
   149  
   150  	addr, err := api.New(context.Background())
   151  	if err == nil {
   152  		t.Fatal("Should have returned an error")
   153  	}
   154  	if addr != (common.Address{}) {
   155  		t.Fatal("Empty address should be returned")
   156  	}
   157  }
   158  
   159  func failCreateAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
   160  	ui.approveCh <- "N"
   161  	addr, err := api.New(context.Background())
   162  	if err != ErrRequestDenied {
   163  		t.Fatal(err)
   164  	}
   165  	if addr != (common.Address{}) {
   166  		t.Fatal("Empty address should be returned")
   167  	}
   168  }
   169  
   170  func list(ui *headlessUi, api *SignerAPI, t *testing.T) ([]common.Address, error) {
   171  	ui.approveCh <- "A"
   172  	return api.List(context.Background())
   173  
   174  }
   175  
   176  func TestNewAcc(t *testing.T) {
   177  	api, control := setup(t)
   178  	verifyNum := func(num int) {
   179  		list, err := list(control, api, t)
   180  		if err != nil {
   181  			t.Errorf("Unexpected error %v", err)
   182  		}
   183  		if len(list) != num {
   184  			t.Errorf("Expected %d accounts, got %d", num, len(list))
   185  		}
   186  	}
   187  	// Testing create and create-deny
   188  	createAccount(control, api, t)
   189  	createAccount(control, api, t)
   190  	failCreateAccount(control, api, t)
   191  	failCreateAccount(control, api, t)
   192  	createAccount(control, api, t)
   193  	failCreateAccount(control, api, t)
   194  	createAccount(control, api, t)
   195  	failCreateAccount(control, api, t)
   196  	verifyNum(4)
   197  
   198  	// Fail to create this, due to bad password
   199  	failCreateAccountWithPassword(control, api, "short", t)
   200  	failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
   201  	verifyNum(4)
   202  
   203  	// Testing listing:
   204  	// Listing one Account
   205  	control.approveCh <- "1"
   206  	list, err := api.List(context.Background())
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	if len(list) != 1 {
   211  		t.Fatalf("List should only show one Account")
   212  	}
   213  	// Listing denied
   214  	control.approveCh <- "Nope"
   215  	list, err = api.List(context.Background())
   216  	if len(list) != 0 {
   217  		t.Fatalf("List should be empty")
   218  	}
   219  	if err != ErrRequestDenied {
   220  		t.Fatal("Expected deny")
   221  	}
   222  }
   223  
   224  func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
   225  	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
   226  	gas := hexutil.Uint64(21000)
   227  	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
   228  	value := (hexutil.Big)(*big.NewInt(1e18))
   229  	nonce := (hexutil.Uint64)(0)
   230  	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
   231  	tx := SendTxArgs{
   232  		From:     from,
   233  		To:       &to,
   234  		Gas:      gas,
   235  		GasPrice: gasPrice,
   236  		Value:    value,
   237  		Data:     &data,
   238  		Nonce:    nonce}
   239  	return tx
   240  }
   241  
   242  func TestSignTx(t *testing.T) {
   243  	var (
   244  		list      []common.Address
   245  		res, res2 *ethapi.SignTransactionResult
   246  		err       error
   247  	)
   248  
   249  	api, control := setup(t)
   250  	createAccount(control, api, t)
   251  	control.approveCh <- "A"
   252  	list, err = api.List(context.Background())
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	a := common.NewMixedcaseAddress(list[0])
   257  
   258  	methodSig := "test(uint)"
   259  	tx := mkTestTx(a)
   260  
   261  	control.approveCh <- "Y"
   262  	control.inputCh <- "wrongpassword"
   263  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   264  	if res != nil {
   265  		t.Errorf("Expected nil-response, got %v", res)
   266  	}
   267  	if err != keystore.ErrDecrypt {
   268  		t.Errorf("Expected ErrLocked! %v", err)
   269  	}
   270  	control.approveCh <- "No way"
   271  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   272  	if res != nil {
   273  		t.Errorf("Expected nil-response, got %v", res)
   274  	}
   275  	if err != ErrRequestDenied {
   276  		t.Errorf("Expected ErrRequestDenied! %v", err)
   277  	}
   278  	// Sign with correct password
   279  	control.approveCh <- "Y"
   280  	control.inputCh <- "a_long_password"
   281  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   282  
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	parsedTx := &types.Transaction{}
   287  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
   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  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
   314  
   315  	//The tx should be modified by the UI
   316  	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
   317  		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
   318  	}
   319  	if bytes.Equal(res.Raw, res2.Raw) {
   320  		t.Error("Expected tx to be modified by UI")
   321  	}
   322  
   323  }