github.com/bcnmy/go-ethereum@v1.10.27/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  	api, control := setup(t)
   173  	verifyNum := func(num int) {
   174  		list, err := list(control, api, t)
   175  		if err != nil {
   176  			t.Errorf("Unexpected error %v", err)
   177  		}
   178  		if len(list) != num {
   179  			t.Errorf("Expected %d accounts, got %d", num, len(list))
   180  		}
   181  	}
   182  	// Testing create and create-deny
   183  	createAccount(control, api, t)
   184  	createAccount(control, api, t)
   185  	failCreateAccount(control, api, t)
   186  	failCreateAccount(control, api, t)
   187  	createAccount(control, api, t)
   188  	failCreateAccount(control, api, t)
   189  	createAccount(control, api, t)
   190  	failCreateAccount(control, api, t)
   191  	verifyNum(4)
   192  
   193  	// Fail to create this, due to bad password
   194  	failCreateAccountWithPassword(control, api, "short", t)
   195  	failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
   196  	verifyNum(4)
   197  
   198  	// Testing listing:
   199  	// Listing one Account
   200  	control.approveCh <- "1"
   201  	list, err := api.List(context.Background())
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	if len(list) != 1 {
   206  		t.Fatalf("List should only show one Account")
   207  	}
   208  	// Listing denied
   209  	control.approveCh <- "Nope"
   210  	list, err = api.List(context.Background())
   211  	if len(list) != 0 {
   212  		t.Fatalf("List should be empty")
   213  	}
   214  	if err != core.ErrRequestDenied {
   215  		t.Fatal("Expected deny")
   216  	}
   217  }
   218  
   219  func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs {
   220  	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
   221  	gas := hexutil.Uint64(21000)
   222  	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
   223  	value := (hexutil.Big)(*big.NewInt(1e18))
   224  	nonce := (hexutil.Uint64)(0)
   225  	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
   226  	tx := apitypes.SendTxArgs{
   227  		From:     from,
   228  		To:       &to,
   229  		Gas:      gas,
   230  		GasPrice: &gasPrice,
   231  		Value:    value,
   232  		Data:     &data,
   233  		Nonce:    nonce}
   234  	return tx
   235  }
   236  
   237  func TestSignTx(t *testing.T) {
   238  	var (
   239  		list      []common.Address
   240  		res, res2 *ethapi.SignTransactionResult
   241  		err       error
   242  	)
   243  
   244  	api, control := setup(t)
   245  	createAccount(control, api, t)
   246  	control.approveCh <- "A"
   247  	list, err = api.List(context.Background())
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	if len(list) == 0 {
   252  		t.Fatal("Unexpected empty list")
   253  	}
   254  	a := common.NewMixedcaseAddress(list[0])
   255  
   256  	methodSig := "test(uint)"
   257  	tx := mkTestTx(a)
   258  
   259  	control.approveCh <- "Y"
   260  	control.inputCh <- "wrongpassword"
   261  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   262  	if res != nil {
   263  		t.Errorf("Expected nil-response, got %v", res)
   264  	}
   265  	if err != keystore.ErrDecrypt {
   266  		t.Errorf("Expected ErrLocked! %v", err)
   267  	}
   268  	control.approveCh <- "No way"
   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 != core.ErrRequestDenied {
   274  		t.Errorf("Expected ErrRequestDenied! %v", err)
   275  	}
   276  	// Sign with correct password
   277  	control.approveCh <- "Y"
   278  	control.inputCh <- "a_long_password"
   279  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   280  
   281  	if err != nil {
   282  		t.Fatal(err)
   283  	}
   284  	parsedTx := &types.Transaction{}
   285  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
   286  
   287  	//The tx should NOT be modified by the UI
   288  	if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
   289  		t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
   290  	}
   291  	control.approveCh <- "Y"
   292  	control.inputCh <- "a_long_password"
   293  
   294  	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	if !bytes.Equal(res.Raw, res2.Raw) {
   299  		t.Error("Expected tx to be unmodified by UI")
   300  	}
   301  
   302  	//The tx is modified by the UI
   303  	control.approveCh <- "M"
   304  	control.inputCh <- "a_long_password"
   305  
   306  	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	parsedTx2 := &types.Transaction{}
   311  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
   312  
   313  	//The tx should be modified by the UI
   314  	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
   315  		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
   316  	}
   317  	if bytes.Equal(res.Raw, res2.Raw) {
   318  		t.Error("Expected tx to be modified by UI")
   319  	}
   320  }