github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/signer/core/api_test.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo 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/aidoskuneen/adk-node/accounts"
    31  	"github.com/aidoskuneen/adk-node/accounts/keystore"
    32  	"github.com/aidoskuneen/adk-node/common"
    33  	"github.com/aidoskuneen/adk-node/common/hexutil"
    34  	"github.com/aidoskuneen/adk-node/core/types"
    35  	"github.com/aidoskuneen/adk-node/internal/ethapi"
    36  	"github.com/aidoskuneen/adk-node/rlp"
    37  	"github.com/aidoskuneen/adk-node/signer/core"
    38  	"github.com/aidoskuneen/adk-node/signer/core/apitypes"
    39  	"github.com/aidoskuneen/adk-node/signer/fourbyte"
    40  	"github.com/aidoskuneen/adk-node/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 ethapi.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("", "eth-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, true, "")
   130  	api := core.NewSignerAPI(am, 1337, true, 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.MixedcaseAddress) apitypes.SendTxArgs {
   228  	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
   229  	gas := hexutil.Uint64(21000)
   230  	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
   231  	value := (hexutil.Big)(*big.NewInt(1e18))
   232  	nonce := (hexutil.Uint64)(0)
   233  	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
   234  	tx := apitypes.SendTxArgs{
   235  		From:     from,
   236  		To:       &to,
   237  		Gas:      gas,
   238  		GasPrice: &gasPrice,
   239  		Value:    value,
   240  		Data:     &data,
   241  		Nonce:    nonce}
   242  	return tx
   243  }
   244  
   245  func TestSignTx(t *testing.T) {
   246  	var (
   247  		list      []common.Address
   248  		res, res2 *ethapi.SignTransactionResult
   249  		err       error
   250  	)
   251  
   252  	api, control := setup(t)
   253  	createAccount(control, api, t)
   254  	control.approveCh <- "A"
   255  	list, err = api.List(context.Background())
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	a := common.NewMixedcaseAddress(list[0])
   260  
   261  	methodSig := "test(uint)"
   262  	tx := mkTestTx(a)
   263  
   264  	control.approveCh <- "Y"
   265  	control.inputCh <- "wrongpassword"
   266  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   267  	if res != nil {
   268  		t.Errorf("Expected nil-response, got %v", res)
   269  	}
   270  	if err != keystore.ErrDecrypt {
   271  		t.Errorf("Expected ErrLocked! %v", err)
   272  	}
   273  	control.approveCh <- "No way"
   274  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   275  	if res != nil {
   276  		t.Errorf("Expected nil-response, got %v", res)
   277  	}
   278  	if err != core.ErrRequestDenied {
   279  		t.Errorf("Expected ErrRequestDenied! %v", err)
   280  	}
   281  	// Sign with correct password
   282  	control.approveCh <- "Y"
   283  	control.inputCh <- "a_long_password"
   284  	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
   285  
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	parsedTx := &types.Transaction{}
   290  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
   291  
   292  	//The tx should NOT be modified by the UI
   293  	if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
   294  		t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
   295  	}
   296  	control.approveCh <- "Y"
   297  	control.inputCh <- "a_long_password"
   298  
   299  	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  	if !bytes.Equal(res.Raw, res2.Raw) {
   304  		t.Error("Expected tx to be unmodified by UI")
   305  	}
   306  
   307  	//The tx is modified by the UI
   308  	control.approveCh <- "M"
   309  	control.inputCh <- "a_long_password"
   310  
   311  	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	parsedTx2 := &types.Transaction{}
   316  	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
   317  
   318  	//The tx should be modified by the UI
   319  	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
   320  		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
   321  	}
   322  	if bytes.Equal(res.Raw, res2.Raw) {
   323  		t.Error("Expected tx to be modified by UI")
   324  	}
   325  
   326  }