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  }