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