github.com/theQRL/go-zond@v0.2.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/theQRL/go-zond/accounts"
    30  	"github.com/theQRL/go-zond/accounts/keystore"
    31  	"github.com/theQRL/go-zond/common"
    32  	"github.com/theQRL/go-zond/common/hexutil"
    33  	"github.com/theQRL/go-zond/core/types"
    34  	"github.com/theQRL/go-zond/internal/zondapi"
    35  	"github.com/theQRL/go-zond/signer/core"
    36  	"github.com/theQRL/go-zond/signer/core/apitypes"
    37  	"github.com/theQRL/go-zond/signer/fourbyte"
    38  	"github.com/theQRL/go-zond/signer/storage"
    39  )
    40  
    41  // Used for testing
    42  type headlessUi struct {
    43  	approveCh chan string // to send approve/deny
    44  	inputCh   chan string // to send password
    45  }
    46  
    47  func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
    48  	input := <-ui.inputCh
    49  	return core.UserInputResponse{Text: input}, nil
    50  }
    51  
    52  func (ui *headlessUi) OnSignerStartup(info core.StartupInfo)         {}
    53  func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI)        {}
    54  func (ui *headlessUi) OnApprovedTx(tx zondapi.SignTransactionResult) {}
    55  
    56  func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
    57  	switch <-ui.approveCh {
    58  	case "Y":
    59  		return core.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 := new(big.Int).Add(&old, big.NewInt(1))
    64  		request.Transaction.Value = hexutil.Big(*newVal)
    65  		return core.SignTxResponse{request.Transaction, true}, nil
    66  	default:
    67  		return core.SignTxResponse{request.Transaction, false}, nil
    68  	}
    69  }
    70  
    71  func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
    72  	approved := (<-ui.approveCh == "Y")
    73  	return core.SignDataResponse{approved}, nil
    74  }
    75  
    76  func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
    77  	approval := <-ui.approveCh
    78  	//fmt.Printf("approval %s\n", approval)
    79  	switch approval {
    80  	case "A":
    81  		return core.ListResponse{request.Accounts}, nil
    82  	case "1":
    83  		l := make([]accounts.Account, 1)
    84  		l[0] = request.Accounts[1]
    85  		return core.ListResponse{l}, nil
    86  	default:
    87  		return core.ListResponse{nil}, nil
    88  	}
    89  }
    90  
    91  func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
    92  	if <-ui.approveCh == "Y" {
    93  		return core.NewAccountResponse{true}, nil
    94  	}
    95  	return core.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 := t.TempDir()
   110  	d, err := filepath.EvalSymlinks(d)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	return d
   115  }
   116  
   117  func setup(t *testing.T) (*core.SignerAPI, *headlessUi) {
   118  	db, err := fourbyte.New()
   119  	if err != nil {
   120  		t.Fatal(err.Error())
   121  	}
   122  	ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
   123  	am := core.StartClefAccountManager(tmpDirName(t) /*false,*/, true /*, ""*/)
   124  	api := core.NewSignerAPI(am, 1337 /*true,*/, ui, db, true, &storage.NoStorage{})
   125  	return api, ui
   126  }
   127  func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
   128  	ui.approveCh <- "Y"
   129  	ui.inputCh <- "a_long_password"
   130  	_, err := api.New(context.Background())
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	// Some time to allow changes to propagate
   135  	time.Sleep(250 * time.Millisecond)
   136  }
   137  
   138  func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) {
   139  	ui.approveCh <- "Y"
   140  	// We will be asked three times to provide a suitable password
   141  	ui.inputCh <- password
   142  	ui.inputCh <- password
   143  	ui.inputCh <- password
   144  
   145  	addr, err := api.New(context.Background())
   146  	if err == nil {
   147  		t.Fatal("Should have returned an error")
   148  	}
   149  	if addr != (common.Address{}) {
   150  		t.Fatal("Empty address should be returned")
   151  	}
   152  }
   153  
   154  func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
   155  	ui.approveCh <- "N"
   156  	addr, err := api.New(context.Background())
   157  	if err != core.ErrRequestDenied {
   158  		t.Fatal(err)
   159  	}
   160  	if addr != (common.Address{}) {
   161  		t.Fatal("Empty address should be returned")
   162  	}
   163  }
   164  
   165  func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) {
   166  	ui.approveCh <- "A"
   167  	return api.List(context.Background())
   168  }
   169  
   170  func TestNewAcc(t *testing.T) {
   171  	api, control := setup(t)
   172  	verifyNum := func(num int) {
   173  		list, err := list(control, api, t)
   174  		if err != nil {
   175  			t.Errorf("Unexpected error %v", err)
   176  		}
   177  		if len(list) != num {
   178  			t.Errorf("Expected %d accounts, got %d", num, len(list))
   179  		}
   180  	}
   181  	// Testing create and create-deny
   182  	createAccount(control, api, t)
   183  	createAccount(control, api, t)
   184  	failCreateAccount(control, api, t)
   185  	failCreateAccount(control, api, t)
   186  	createAccount(control, api, t)
   187  	failCreateAccount(control, api, t)
   188  	createAccount(control, api, t)
   189  	failCreateAccount(control, api, t)
   190  	verifyNum(4)
   191  
   192  	// Fail to create this, due to bad password
   193  	failCreateAccountWithPassword(control, api, "short", t)
   194  	failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
   195  	verifyNum(4)
   196  
   197  	// Testing listing:
   198  	// Listing one Account
   199  	control.approveCh <- "1"
   200  	list, err := api.List(context.Background())
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	if len(list) != 1 {
   205  		t.Fatalf("List should only show one Account")
   206  	}
   207  	// Listing denied
   208  	control.approveCh <- "Nope"
   209  	list, err = api.List(context.Background())
   210  	if len(list) != 0 {
   211  		t.Fatalf("List should be empty")
   212  	}
   213  	if err != core.ErrRequestDenied {
   214  		t.Fatal("Expected deny")
   215  	}
   216  }
   217  
   218  func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs {
   219  	address, _ := common.NewAddressFromString("Z0000000000000000000000000000000000001337")
   220  	to := common.NewMixedcaseAddress(address)
   221  	gas := hexutil.Uint64(21000)
   222  	maxFeePerGas := (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  		MaxFeePerGas: &maxFeePerGas,
   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 *zondapi.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  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  
   284  	parsedTx := &types.Transaction{}
   285  	if err := parsedTx.UnmarshalBinary(res.Raw); err != nil {
   286  		t.Fatal(err)
   287  	}
   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  	if err := parsedTx2.UnmarshalBinary(res.Raw); err != nil {
   314  		t.Fatal(err)
   315  	}
   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  }