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