b5d471a739
* clef: initial implementation of bidirectional RPC communication for the UI * signer: fix tests to pass + formatting * clef: fix unused import + formatting * signer: gosimple nitpicks
372 lines
9.4 KiB
Go
372 lines
9.4 KiB
Go
// Copyright 2018 The go-ethereum Authors
|
|
// This file is part of go-ethereum.
|
|
//
|
|
// go-ethereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// go-ethereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
)
|
|
|
|
//Used for testing
|
|
type HeadlessUI struct {
|
|
controller chan string
|
|
}
|
|
|
|
func (ui *HeadlessUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
|
return UserInputResponse{}, errors.New("not implemented")
|
|
}
|
|
|
|
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) {
|
|
}
|
|
func (ui *HeadlessUI) RegisterUIServer(api *UIServerAPI) {
|
|
}
|
|
|
|
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|
fmt.Printf("OnApproved()\n")
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
|
|
|
switch <-ui.controller {
|
|
case "Y":
|
|
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
|
|
case "M": //Modify
|
|
old := big.Int(request.Transaction.Value)
|
|
newVal := big.NewInt(0).Add(&old, big.NewInt(1))
|
|
request.Transaction.Value = hexutil.Big(*newVal)
|
|
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
|
|
default:
|
|
return SignTxResponse{request.Transaction, false, ""}, nil
|
|
}
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
|
if "Y" == <-ui.controller {
|
|
return SignDataResponse{true, <-ui.controller}, nil
|
|
}
|
|
return SignDataResponse{false, ""}, nil
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
|
return ExportResponse{<-ui.controller == "Y"}, nil
|
|
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
|
if "Y" == <-ui.controller {
|
|
return ImportResponse{true, <-ui.controller, <-ui.controller}, nil
|
|
}
|
|
return ImportResponse{false, "", ""}, nil
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
|
switch <-ui.controller {
|
|
case "A":
|
|
return ListResponse{request.Accounts}, nil
|
|
case "1":
|
|
l := make([]accounts.Account, 1)
|
|
l[0] = request.Accounts[1]
|
|
return ListResponse{l}, nil
|
|
default:
|
|
return ListResponse{nil}, nil
|
|
}
|
|
}
|
|
|
|
func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
|
if "Y" == <-ui.controller {
|
|
return NewAccountResponse{true, <-ui.controller}, nil
|
|
}
|
|
return NewAccountResponse{false, ""}, nil
|
|
}
|
|
|
|
func (ui *HeadlessUI) ShowError(message string) {
|
|
//stdout is used by communication
|
|
fmt.Fprintln(os.Stderr, message)
|
|
}
|
|
|
|
func (ui *HeadlessUI) ShowInfo(message string) {
|
|
//stdout is used by communication
|
|
fmt.Fprintln(os.Stderr, message)
|
|
}
|
|
|
|
func tmpDirName(t *testing.T) string {
|
|
d, err := ioutil.TempDir("", "eth-keystore-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, err = filepath.EvalSymlinks(d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return d
|
|
}
|
|
|
|
func setup(t *testing.T) (*SignerAPI, chan string) {
|
|
|
|
controller := make(chan string, 20)
|
|
|
|
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
var (
|
|
ui = &HeadlessUI{controller}
|
|
am = StartClefAccountManager(tmpDirName(t), true, true)
|
|
api = NewSignerAPI(am, 1337, true, ui, db, true)
|
|
)
|
|
return api, controller
|
|
}
|
|
func createAccount(control chan string, api *SignerAPI, t *testing.T) {
|
|
|
|
control <- "Y"
|
|
control <- "a_long_password"
|
|
_, err := api.New(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Some time to allow changes to propagate
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
|
|
func failCreateAccountWithPassword(control chan string, api *SignerAPI, password string, t *testing.T) {
|
|
|
|
control <- "Y"
|
|
control <- password
|
|
control <- "Y"
|
|
control <- password
|
|
control <- "Y"
|
|
control <- password
|
|
|
|
addr, err := api.New(context.Background())
|
|
if err == nil {
|
|
t.Fatal("Should have returned an error")
|
|
}
|
|
if addr != (common.Address{}) {
|
|
t.Fatal("Empty address should be returned")
|
|
}
|
|
}
|
|
|
|
func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) {
|
|
control <- "N"
|
|
addr, err := api.New(context.Background())
|
|
if err != ErrRequestDenied {
|
|
t.Fatal(err)
|
|
}
|
|
if addr != (common.Address{}) {
|
|
t.Fatal("Empty address should be returned")
|
|
}
|
|
}
|
|
|
|
func list(control chan string, api *SignerAPI, t *testing.T) []common.Address {
|
|
control <- "A"
|
|
list, err := api.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return list
|
|
}
|
|
|
|
func TestNewAcc(t *testing.T) {
|
|
api, control := setup(t)
|
|
verifyNum := func(num int) {
|
|
if list := list(control, api, t); len(list) != num {
|
|
t.Errorf("Expected %d accounts, got %d", num, len(list))
|
|
}
|
|
}
|
|
// Testing create and create-deny
|
|
createAccount(control, api, t)
|
|
createAccount(control, api, t)
|
|
failCreateAccount(control, api, t)
|
|
failCreateAccount(control, api, t)
|
|
createAccount(control, api, t)
|
|
failCreateAccount(control, api, t)
|
|
createAccount(control, api, t)
|
|
failCreateAccount(control, api, t)
|
|
|
|
verifyNum(4)
|
|
|
|
// Fail to create this, due to bad password
|
|
failCreateAccountWithPassword(control, api, "short", t)
|
|
failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
|
|
|
|
verifyNum(4)
|
|
|
|
// Testing listing:
|
|
// Listing one Account
|
|
control <- "1"
|
|
list, err := api.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(list) != 1 {
|
|
t.Fatalf("List should only show one Account")
|
|
}
|
|
// Listing denied
|
|
control <- "Nope"
|
|
list, err = api.List(context.Background())
|
|
if len(list) != 0 {
|
|
t.Fatalf("List should be empty")
|
|
}
|
|
if err != ErrRequestDenied {
|
|
t.Fatal("Expected deny")
|
|
}
|
|
}
|
|
|
|
func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
|
|
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
|
|
gas := hexutil.Uint64(21000)
|
|
gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
|
|
value := (hexutil.Big)(*big.NewInt(1e18))
|
|
nonce := (hexutil.Uint64)(0)
|
|
data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
|
|
tx := SendTxArgs{
|
|
From: from,
|
|
To: &to,
|
|
Gas: gas,
|
|
GasPrice: gasPrice,
|
|
Value: value,
|
|
Data: &data,
|
|
Nonce: nonce}
|
|
return tx
|
|
}
|
|
|
|
func TestSignTx(t *testing.T) {
|
|
var (
|
|
list []common.Address
|
|
res, res2 *ethapi.SignTransactionResult
|
|
err error
|
|
)
|
|
|
|
api, control := setup(t)
|
|
createAccount(control, api, t)
|
|
control <- "A"
|
|
list, err = api.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a := common.NewMixedcaseAddress(list[0])
|
|
|
|
methodSig := "test(uint)"
|
|
tx := mkTestTx(a)
|
|
|
|
control <- "Y"
|
|
control <- "wrongpassword"
|
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
|
if res != nil {
|
|
t.Errorf("Expected nil-response, got %v", res)
|
|
}
|
|
if err != keystore.ErrDecrypt {
|
|
t.Errorf("Expected ErrLocked! %v", err)
|
|
}
|
|
control <- "No way"
|
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
|
if res != nil {
|
|
t.Errorf("Expected nil-response, got %v", res)
|
|
}
|
|
if err != ErrRequestDenied {
|
|
t.Errorf("Expected ErrRequestDenied! %v", err)
|
|
}
|
|
control <- "Y"
|
|
control <- "a_long_password"
|
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parsedTx := &types.Transaction{}
|
|
rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
|
|
|
|
//The tx should NOT be modified by the UI
|
|
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
|
|
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
|
|
}
|
|
control <- "Y"
|
|
control <- "a_long_password"
|
|
|
|
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(res.Raw, res2.Raw) {
|
|
t.Error("Expected tx to be unmodified by UI")
|
|
}
|
|
|
|
//The tx is modified by the UI
|
|
control <- "M"
|
|
control <- "a_long_password"
|
|
|
|
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parsedTx2 := &types.Transaction{}
|
|
rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
|
|
|
|
//The tx should be modified by the UI
|
|
if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
|
|
t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
|
|
}
|
|
if bytes.Equal(res.Raw, res2.Raw) {
|
|
t.Error("Expected tx to be modified by UI")
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
func TestAsyncronousResponses(t *testing.T){
|
|
|
|
//Set up one account
|
|
api, control := setup(t)
|
|
createAccount(control, api, t)
|
|
|
|
// Two transactions, the second one with larger value than the first
|
|
tx1 := mkTestTx()
|
|
newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1))
|
|
tx2 := mkTestTx()
|
|
tx2.Value = (*hexutil.Big)(newVal)
|
|
|
|
control <- "W" //wait
|
|
control <- "Y" //
|
|
control <- "a_long_password"
|
|
control <- "Y" //
|
|
control <- "a_long_password"
|
|
|
|
var err error
|
|
|
|
h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil)
|
|
h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil)
|
|
|
|
|
|
}
|
|
*/
|