github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/signer/rules/rules_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  //
    17  package rules
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/vntchain/go-vnt/accounts"
    26  	"github.com/vntchain/go-vnt/common"
    27  	"github.com/vntchain/go-vnt/common/hexutil"
    28  	"github.com/vntchain/go-vnt/core/types"
    29  	"github.com/vntchain/go-vnt/internal/vntapi"
    30  	"github.com/vntchain/go-vnt/signer/core"
    31  	"github.com/vntchain/go-vnt/signer/storage"
    32  )
    33  
    34  const JS = `
    35  /**
    36  This is an example implementation of a Javascript rule file.
    37  
    38  When the signer receives a request over the external API, the corresponding method is evaluated.
    39  Three things can happen:
    40  
    41  1. The method returns "Approve". This means the operation is permitted.
    42  2. The method returns "Reject". This means the operation is rejected.
    43  3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
    44  that the operation will continue to manual processing, via the regular UI method chosen by the user.
    45  
    46  [*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
    47  only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
    48  accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
    49  
    50  **/
    51  
    52  function ApproveListing(request){
    53  	console.log("In js approve listing");
    54  	console.log(request.accounts[3].Address)
    55  	console.log(request.meta.Remote)
    56  	return "Approve"
    57  }
    58  
    59  function ApproveTx(request){
    60  	console.log("test");
    61  	console.log("from");
    62  	return "Reject";
    63  }
    64  
    65  function test(thing){
    66  	console.log(thing.String())
    67  }
    68  
    69  `
    70  
    71  func mixAddr(a string) (*common.MixedcaseAddress, error) {
    72  	return common.NewMixedcaseAddressFromString(a)
    73  }
    74  
    75  type alwaysDenyUI struct{}
    76  
    77  func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
    78  }
    79  
    80  func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
    81  	return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
    82  }
    83  
    84  func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
    85  	return core.SignDataResponse{Approved: false, Password: ""}, nil
    86  }
    87  
    88  func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
    89  	return core.ExportResponse{Approved: false}, nil
    90  }
    91  
    92  func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
    93  	return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
    94  }
    95  
    96  func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
    97  	return core.ListResponse{Accounts: nil}, nil
    98  }
    99  
   100  func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
   101  	return core.NewAccountResponse{Approved: false, Password: ""}, nil
   102  }
   103  
   104  func (alwaysDenyUI) ShowError(message string) {
   105  	panic("implement me")
   106  }
   107  
   108  func (alwaysDenyUI) ShowInfo(message string) {
   109  	panic("implement me")
   110  }
   111  
   112  func (alwaysDenyUI) OnApprovedTx(tx vntapi.SignTransactionResult) {
   113  	panic("implement me")
   114  }
   115  
   116  func initRuleEngine(js string) (*rulesetUI, error) {
   117  	r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
   118  	if err != nil {
   119  		return nil, fmt.Errorf("failed to create js engine: %v", err)
   120  	}
   121  	if err = r.Init(js); err != nil {
   122  		return nil, fmt.Errorf("failed to load bootstrap js: %v", err)
   123  	}
   124  	return r, nil
   125  }
   126  
   127  func TestListRequest(t *testing.T) {
   128  	accs := make([]core.Account, 5)
   129  
   130  	for i := range accs {
   131  		addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i)
   132  		acc := core.Account{
   133  			Address: common.BytesToAddress(common.Hex2Bytes(addr)),
   134  			URL:     accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)},
   135  		}
   136  		accs[i] = acc
   137  	}
   138  
   139  	js := `function ApproveListing(){ return "Approve" }`
   140  
   141  	r, err := initRuleEngine(js)
   142  	if err != nil {
   143  		t.Errorf("Couldn't create evaluator %v", err)
   144  		return
   145  	}
   146  	resp, err := r.ApproveListing(&core.ListRequest{
   147  		Accounts: accs,
   148  		Meta:     core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
   149  	})
   150  	if len(resp.Accounts) != len(accs) {
   151  		t.Errorf("Expected check to resolve to 'Approve'")
   152  	}
   153  }
   154  
   155  func TestSignTxRequest(t *testing.T) {
   156  
   157  	js := `
   158  	function ApproveTx(r){
   159  		console.log("transaction.from", r.transaction.from);
   160  		console.log("transaction.to", r.transaction.to);
   161  		console.log("transaction.value", r.transaction.value);
   162  		console.log("transaction.nonce", r.transaction.nonce);
   163  		if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
   164  		if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
   165  	}`
   166  
   167  	r, err := initRuleEngine(js)
   168  	if err != nil {
   169  		t.Errorf("Couldn't create evaluator %v", err)
   170  		return
   171  	}
   172  	to, err := mixAddr("000000000000000000000000000000000000dead")
   173  	if err != nil {
   174  		t.Error(err)
   175  		return
   176  	}
   177  	from, err := mixAddr("0000000000000000000000000000000000001337")
   178  
   179  	if err != nil {
   180  		t.Error(err)
   181  		return
   182  	}
   183  	fmt.Printf("to %v", to.Address().String())
   184  	resp, err := r.ApproveTx(&core.SignTxRequest{
   185  		Transaction: core.SendTxArgs{
   186  			From: *from,
   187  			To:   to},
   188  		Callinfo: nil,
   189  		Meta:     core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
   190  	})
   191  	if err != nil {
   192  		t.Errorf("Unexpected error %v", err)
   193  	}
   194  	if !resp.Approved {
   195  		t.Errorf("Expected check to resolve to 'Approve'")
   196  	}
   197  }
   198  
   199  type dummyUI struct {
   200  	calls []string
   201  }
   202  
   203  func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
   204  	d.calls = append(d.calls, "ApproveTx")
   205  	return core.SignTxResponse{}, core.ErrRequestDenied
   206  }
   207  
   208  func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
   209  	d.calls = append(d.calls, "ApproveSignData")
   210  	return core.SignDataResponse{}, core.ErrRequestDenied
   211  }
   212  
   213  func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
   214  	d.calls = append(d.calls, "ApproveExport")
   215  	return core.ExportResponse{}, core.ErrRequestDenied
   216  }
   217  
   218  func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
   219  	d.calls = append(d.calls, "ApproveImport")
   220  	return core.ImportResponse{}, core.ErrRequestDenied
   221  }
   222  
   223  func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
   224  	d.calls = append(d.calls, "ApproveListing")
   225  	return core.ListResponse{}, core.ErrRequestDenied
   226  }
   227  
   228  func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
   229  	d.calls = append(d.calls, "ApproveNewAccount")
   230  	return core.NewAccountResponse{}, core.ErrRequestDenied
   231  }
   232  
   233  func (d *dummyUI) ShowError(message string) {
   234  	d.calls = append(d.calls, "ShowError")
   235  }
   236  
   237  func (d *dummyUI) ShowInfo(message string) {
   238  	d.calls = append(d.calls, "ShowInfo")
   239  }
   240  
   241  func (d *dummyUI) OnApprovedTx(tx vntapi.SignTransactionResult) {
   242  	d.calls = append(d.calls, "OnApprovedTx")
   243  }
   244  func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
   245  }
   246  
   247  //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
   248  func TestForwarding(t *testing.T) {
   249  
   250  	js := ""
   251  	ui := &dummyUI{make([]string, 0)}
   252  	jsBackend := storage.NewEphemeralStorage()
   253  	credBackend := storage.NewEphemeralStorage()
   254  	r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
   255  	if err != nil {
   256  		t.Fatalf("Failed to create js engine: %v", err)
   257  	}
   258  	if err = r.Init(js); err != nil {
   259  		t.Fatalf("Failed to load bootstrap js: %v", err)
   260  	}
   261  	r.ApproveSignData(nil)
   262  	r.ApproveTx(nil)
   263  	r.ApproveImport(nil)
   264  	r.ApproveNewAccount(nil)
   265  	r.ApproveListing(nil)
   266  	r.ApproveExport(nil)
   267  	r.ShowError("test")
   268  	r.ShowInfo("test")
   269  
   270  	//This one is not forwarded
   271  	r.OnApprovedTx(vntapi.SignTransactionResult{})
   272  
   273  	expCalls := 8
   274  	if len(ui.calls) != expCalls {
   275  
   276  		t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
   277  
   278  	}
   279  
   280  }
   281  
   282  func TestMissingFunc(t *testing.T) {
   283  	r, err := initRuleEngine(JS)
   284  	if err != nil {
   285  		t.Errorf("Couldn't create evaluator %v", err)
   286  		return
   287  	}
   288  
   289  	_, err = r.execute("MissingMethod", "test")
   290  
   291  	if err == nil {
   292  		t.Error("Expected error")
   293  	}
   294  
   295  	approved, err := r.checkApproval("MissingMethod", nil, nil)
   296  	if err == nil {
   297  		t.Errorf("Expected missing method to yield error'")
   298  	}
   299  	if approved {
   300  		t.Errorf("Expected missing method to cause non-approval")
   301  	}
   302  	fmt.Printf("Err %v", err)
   303  
   304  }
   305  func TestStorage(t *testing.T) {
   306  
   307  	js := `
   308  	function testStorage(){
   309  		storage.Put("mykey", "myvalue")
   310  		a = storage.Get("mykey")
   311  
   312  		storage.Put("mykey", ["a", "list"])  	// Should result in "a,list"
   313  		a += storage.Get("mykey")
   314  
   315  
   316  		storage.Put("mykey", {"an": "object"}) 	// Should result in "[object Object]"
   317  		a += storage.Get("mykey")
   318  
   319  
   320  		storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
   321  		a += storage.Get("mykey")
   322  
   323  		a += storage.Get("missingkey")		//Missing keys should result in empty string
   324  		storage.Put("","missing key==noop") // Can't store with 0-length key
   325  		a += storage.Get("")				// Should result in ''
   326  
   327  		var b = new BigNumber(2)
   328  		var c = new BigNumber(16)//"0xf0",16)
   329  		var d = b.plus(c)
   330  		console.log(d)
   331  		return a
   332  	}
   333  `
   334  	r, err := initRuleEngine(js)
   335  	if err != nil {
   336  		t.Errorf("Couldn't create evaluator %v", err)
   337  		return
   338  	}
   339  
   340  	v, err := r.execute("testStorage", nil)
   341  
   342  	if err != nil {
   343  		t.Errorf("Unexpected error %v", err)
   344  	}
   345  
   346  	retval, err := v.ToString()
   347  
   348  	if err != nil {
   349  		t.Errorf("Unexpected error %v", err)
   350  	}
   351  	exp := `myvaluea,list[object Object]{"an":"object"}`
   352  	if retval != exp {
   353  		t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval)
   354  	}
   355  	fmt.Printf("Err %v", err)
   356  
   357  }
   358  
   359  const ExampleTxWindow = `
   360  	function big(str){
   361  		if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
   362  		return new BigNumber(str)
   363  	}
   364  
   365  	// Time window: 1 week
   366  	var window = 1000* 3600*24*7;
   367  
   368  	// Limit : 1 vnt
   369  	var limit = new BigNumber("1e18");
   370  
   371  	function isLimitOk(transaction){
   372  		var value = big(transaction.value)
   373  		// Start of our window function
   374  		var windowstart = new Date().getTime() - window;
   375  
   376  		var txs = [];
   377  		var stored = storage.Get('txs');
   378  
   379  		if(stored != ""){
   380  			txs = JSON.parse(stored)
   381  		}
   382  		// First, remove all that have passed out of the time-window
   383  		var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
   384  		console.log(txs, newtxs.length);
   385  
   386  		// Secondly, aggregate the current sum
   387  		sum = new BigNumber(0)
   388  
   389  		sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
   390  		console.log("ApproveTx > Sum so far", sum);
   391  		console.log("ApproveTx > Requested", value.toNumber());
   392  
   393  		// Would we exceed weekly limit ?
   394  		return sum.plus(value).lt(limit)
   395  
   396  	}
   397  	function ApproveTx(r){
   398  		console.log(r)
   399  		console.log(typeof(r))
   400  		if (isLimitOk(r.transaction)){
   401  			return "Approve"
   402  		}
   403  		return "Nope"
   404  	}
   405  
   406  	/**
   407  	* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
   408   	* 'response_str' contains the return value that will be sent to the external caller.
   409  	* The return value from this method is ignore - the reason for having this callback is to allow the
   410  	* ruleset to keep track of approved transactions.
   411  	*
   412  	* When implementing rate-limited rules, this callback should be used.
   413  	* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
   414  	* then accepts the transaction, this method will be called.
   415  	*
   416  	* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
   417  	*/
   418   	function OnApprovedTx(resp){
   419  		var value = big(resp.tx.value)
   420  		var txs = []
   421  		// Load stored transactions
   422  		var stored = storage.Get('txs');
   423  		if(stored != ""){
   424  			txs = JSON.parse(stored)
   425  		}
   426  		// Add this to the storage
   427  		txs.push({tstamp: new Date().getTime(), value: value});
   428  		storage.Put("txs", JSON.stringify(txs));
   429  	}
   430  
   431  `
   432  
   433  func dummyTx(value hexutil.Big) *core.SignTxRequest {
   434  
   435  	to, _ := mixAddr("000000000000000000000000000000000000dead")
   436  	from, _ := mixAddr("000000000000000000000000000000000000dead")
   437  	n := hexutil.Uint64(3)
   438  	gas := hexutil.Uint64(21000)
   439  	gasPrice := hexutil.Big(*big.NewInt(2000000))
   440  
   441  	return &core.SignTxRequest{
   442  		Transaction: core.SendTxArgs{
   443  			From:     *from,
   444  			To:       to,
   445  			Value:    value,
   446  			Nonce:    n,
   447  			GasPrice: gasPrice,
   448  			Gas:      gas,
   449  		},
   450  		Callinfo: []core.ValidationInfo{
   451  			{Typ: "Warning", Message: "All your base are bellong to us"},
   452  		},
   453  		Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
   454  	}
   455  }
   456  func dummyTxWithV(value uint64) *core.SignTxRequest {
   457  
   458  	v := big.NewInt(0).SetUint64(value)
   459  	h := hexutil.Big(*v)
   460  	return dummyTx(h)
   461  }
   462  func dummySigned(value *big.Int) *types.Transaction {
   463  	to := common.HexToAddress("000000000000000000000000000000000000dead")
   464  	gas := uint64(21000)
   465  	gasPrice := big.NewInt(2000000)
   466  	data := make([]byte, 0)
   467  	return types.NewTransaction(3, to, value, gas, gasPrice, data)
   468  
   469  }
   470  func TestLimitWindow(t *testing.T) {
   471  
   472  	r, err := initRuleEngine(ExampleTxWindow)
   473  	if err != nil {
   474  		t.Errorf("Couldn't create evaluator %v", err)
   475  		return
   476  	}
   477  
   478  	// 0.3 vnt: 429D069189E0000 wei
   479  	v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000"))
   480  	h := hexutil.Big(*v)
   481  	// The first three should succeed
   482  	for i := 0; i < 3; i++ {
   483  		unsigned := dummyTx(h)
   484  		resp, err := r.ApproveTx(unsigned)
   485  		if err != nil {
   486  			t.Errorf("Unexpected error %v", err)
   487  		}
   488  		if !resp.Approved {
   489  			t.Errorf("Expected check to resolve to 'Approve'")
   490  		}
   491  		// Create a dummy signed transaction
   492  
   493  		response := vntapi.SignTransactionResult{
   494  			Tx:  dummySigned(v),
   495  			Raw: common.Hex2Bytes("deadbeef"),
   496  		}
   497  		r.OnApprovedTx(response)
   498  	}
   499  	// Fourth should fail
   500  	resp, err := r.ApproveTx(dummyTx(h))
   501  	if resp.Approved {
   502  		t.Errorf("Expected check to resolve to 'Reject'")
   503  	}
   504  
   505  }
   506  
   507  // dontCallMe is used as a next-handler that does not want to be called - it invokes test failure
   508  type dontCallMe struct {
   509  	t *testing.T
   510  }
   511  
   512  func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) {
   513  }
   514  
   515  func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
   516  	d.t.Fatalf("Did not expect next-handler to be called")
   517  	return core.SignTxResponse{}, core.ErrRequestDenied
   518  }
   519  
   520  func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
   521  	d.t.Fatalf("Did not expect next-handler to be called")
   522  	return core.SignDataResponse{}, core.ErrRequestDenied
   523  }
   524  
   525  func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
   526  	d.t.Fatalf("Did not expect next-handler to be called")
   527  	return core.ExportResponse{}, core.ErrRequestDenied
   528  }
   529  
   530  func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
   531  	d.t.Fatalf("Did not expect next-handler to be called")
   532  	return core.ImportResponse{}, core.ErrRequestDenied
   533  }
   534  
   535  func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
   536  	d.t.Fatalf("Did not expect next-handler to be called")
   537  	return core.ListResponse{}, core.ErrRequestDenied
   538  }
   539  
   540  func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
   541  	d.t.Fatalf("Did not expect next-handler to be called")
   542  	return core.NewAccountResponse{}, core.ErrRequestDenied
   543  }
   544  
   545  func (d *dontCallMe) ShowError(message string) {
   546  	d.t.Fatalf("Did not expect next-handler to be called")
   547  }
   548  
   549  func (d *dontCallMe) ShowInfo(message string) {
   550  	d.t.Fatalf("Did not expect next-handler to be called")
   551  }
   552  
   553  func (d *dontCallMe) OnApprovedTx(tx vntapi.SignTransactionResult) {
   554  	d.t.Fatalf("Did not expect next-handler to be called")
   555  }
   556  
   557  //TestContextIsCleared tests that the rule-engine does not retain variables over several requests.
   558  // if it does, that would be bad since developers may rely on that to store data,
   559  // instead of using the disk-based data storage
   560  func TestContextIsCleared(t *testing.T) {
   561  
   562  	js := `
   563  	function ApproveTx(){
   564  		if (typeof foobar == 'undefined') {
   565  			foobar = "Approve"
   566   		}
   567  		console.log(foobar)
   568  		if (foobar == "Approve"){
   569  			foobar = "Reject"
   570  		}else{
   571  			foobar = "Approve"
   572  		}
   573  		return foobar
   574  	}
   575  	`
   576  	ui := &dontCallMe{t}
   577  	r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
   578  	if err != nil {
   579  		t.Fatalf("Failed to create js engine: %v", err)
   580  	}
   581  	if err = r.Init(js); err != nil {
   582  		t.Fatalf("Failed to load bootstrap js: %v", err)
   583  	}
   584  	tx := dummyTxWithV(0)
   585  	r1, err := r.ApproveTx(tx)
   586  	r2, err := r.ApproveTx(tx)
   587  	if r1.Approved != r2.Approved {
   588  		t.Errorf("Expected execution context to be cleared between executions")
   589  	}
   590  }
   591  
   592  func TestSignData(t *testing.T) {
   593  
   594  	js := `function ApproveListing(){
   595      return "Approve"
   596  }
   597  function ApproveSignData(r){
   598      if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
   599      {
   600          if(r.message.indexOf("bazonk") >= 0){
   601              return "Approve"
   602          }
   603          return "Reject"
   604      }
   605      // Otherwise goes to manual processing
   606  }`
   607  	r, err := initRuleEngine(js)
   608  	if err != nil {
   609  		t.Errorf("Couldn't create evaluator %v", err)
   610  		return
   611  	}
   612  	message := []byte("baz bazonk foo")
   613  	hash, msg := core.SignHash(message)
   614  	raw := hexutil.Bytes(message)
   615  	addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa")
   616  
   617  	fmt.Printf("address %v %v\n", addr.String(), addr.Original())
   618  	resp, err := r.ApproveSignData(&core.SignDataRequest{
   619  		Address: *addr,
   620  		Message: msg,
   621  		Hash:    hash,
   622  		Meta:    core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
   623  		Rawdata: raw,
   624  	})
   625  	if err != nil {
   626  		t.Fatalf("Unexpected error %v", err)
   627  	}
   628  	if !resp.Approved {
   629  		t.Fatalf("Expected approved")
   630  	}
   631  }