mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
signer, rules, storage: implement rules + ephemeral storage for signer rules
This commit is contained in:
parent
3a9bfba76a
commit
da312a1dcb
6 changed files with 810 additions and 0 deletions
4
cmd/signer/rules/deps/bignumber.js
Normal file
4
cmd/signer/rules/deps/bignumber.js
Normal file
File diff suppressed because one or more lines are too long
235
cmd/signer/rules/deps/bindata.go
Normal file
235
cmd/signer/rules/deps/bindata.go
Normal file
File diff suppressed because one or more lines are too long
21
cmd/signer/rules/deps/deps.go
Normal file
21
cmd/signer/rules/deps/deps.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package deps contains the console JavaScript dependencies Go embedded.
|
||||||
|
package deps
|
||||||
|
|
||||||
|
//go:generate go-bindata -nometadata -pkg deps -o bindata.go bignumber.js
|
||||||
|
//go:generate gofmt -w -s bindata.go
|
||||||
158
cmd/signer/rules/rules.go
Normal file
158
cmd/signer/rules/rules.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// Copyright 2017 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 rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/signer"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/signer/rules/deps"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/signer/storage"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
BigNumber_JS = deps.MustAsset("bignumber.js")
|
||||||
|
)
|
||||||
|
|
||||||
|
// consoleOutput is an override for the console.log and console.error methods to
|
||||||
|
// stream the output into the configured output stream instead of stdout.
|
||||||
|
func consoleOutput(call otto.FunctionCall) otto.Value {
|
||||||
|
output := []string{"JS:> "}
|
||||||
|
for _, argument := range call.ArgumentList {
|
||||||
|
output = append(output, fmt.Sprintf("%v", argument))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stdout, strings.Join(output, " "))
|
||||||
|
return otto.Value{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rulesetUi provides an implementation of SignerUI that evaluates a javascript
|
||||||
|
// file for each defined UI-method
|
||||||
|
type rulesetUi struct {
|
||||||
|
vm *otto.Otto // The JS vm
|
||||||
|
next signer.SignerUI // The next handler, for manual processing
|
||||||
|
storage storage.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRuleEvaluator() (*rulesetUi, error) {
|
||||||
|
c := &rulesetUi{
|
||||||
|
vm: otto.New(),
|
||||||
|
storage: storage.NewEphemeralStorage(),
|
||||||
|
}
|
||||||
|
consoleObj, _ := c.vm.Get("console")
|
||||||
|
consoleObj.Object().Set("log", consoleOutput)
|
||||||
|
consoleObj.Object().Set("error", consoleOutput)
|
||||||
|
|
||||||
|
c.vm.Set("storage", c.storage)
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) Init(javascriptRules string) error {
|
||||||
|
script, err := r.vm.Compile("bignumber.js", BigNumber_JS)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed loading libraries", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.vm.Run(script)
|
||||||
|
|
||||||
|
_, err = r.vm.Run(javascriptRules)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Execution failed", "err", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) checkApproval(jsfunc string, jsarg []byte, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v, err := r.vm.Call("ApproveTx", nil, string(jsarg))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Info("error occurred during execution", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result, err := v.ToString()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("error occurred during response unmarshalling", "error", err)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
if result == "Approve" {
|
||||||
|
log.Info("Op approved")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("rejected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveTx(request *signer.SignTxRequest) (signer.SignTxResponse, error) {
|
||||||
|
jsonreq, err := json.Marshal(request)
|
||||||
|
if err = r.checkApproval("ApproveTx", jsonreq, err); err == nil {
|
||||||
|
return signer.SignTxResponse{Transaction: request.Transaction, From: request.From, Approved: true, Password: ""}, nil
|
||||||
|
}
|
||||||
|
return signer.SignTxResponse{Approved: false}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveSignData(request *signer.SignDataRequest) (signer.SignDataResponse, error) {
|
||||||
|
jsonreq, err := json.Marshal(request)
|
||||||
|
if err = r.checkApproval("ApproveTx", jsonreq, err); err == nil {
|
||||||
|
return signer.SignDataResponse{Approved: true, Password: ""}, nil
|
||||||
|
}
|
||||||
|
return signer.SignDataResponse{Approved: false, Password: ""}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveExport(request *signer.ExportRequest) (signer.ExportResponse, error) {
|
||||||
|
jsonreq, err := json.Marshal(request)
|
||||||
|
if err = r.checkApproval("ApproveTx", jsonreq, err); err == nil {
|
||||||
|
return signer.ExportResponse{Approved: true}, nil
|
||||||
|
}
|
||||||
|
return signer.ExportResponse{Approved: false}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveImport(request *signer.ImportRequest) (signer.ImportResponse, error) {
|
||||||
|
// This cannot be handled by rules, requires setting a password
|
||||||
|
// dispatch to next
|
||||||
|
return r.next.ApproveImport(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveListing(request *signer.ListRequest) (signer.ListResponse, error) {
|
||||||
|
jsonreq, err := json.Marshal(request)
|
||||||
|
if err = r.checkApproval("ApproveListing", jsonreq, err); err == nil {
|
||||||
|
return signer.ListResponse{Accounts: request.Accounts}, nil
|
||||||
|
}
|
||||||
|
return signer.ListResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ApproveNewAccount(request *signer.NewAccountRequest) (signer.NewAccountResponse, error) {
|
||||||
|
// This cannot be handled by rules, requires setting a password
|
||||||
|
// dispatch to next
|
||||||
|
return r.next.ApproveNewAccount(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ShowError(message string) {
|
||||||
|
log.Error(message)
|
||||||
|
r.next.ShowError(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rulesetUi) ShowInfo(message string) {
|
||||||
|
log.Info(message)
|
||||||
|
r.next.ShowInfo(message)
|
||||||
|
}
|
||||||
316
cmd/signer/rules/rules_test.go
Normal file
316
cmd/signer/rules/rules_test.go
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/signer"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const JS = `
|
||||||
|
/**
|
||||||
|
This is an example implementation of a Javascript rule file.
|
||||||
|
|
||||||
|
When the signer receives a request over the external API, the corresponding method is evaluated.
|
||||||
|
Three things can happen:
|
||||||
|
|
||||||
|
1. The method returns "Approve". This means the operation is permitted.
|
||||||
|
2. The method returns "Reject". This means the operation is rejected.
|
||||||
|
3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
|
||||||
|
that the operation will continue to manual processing, via the regular UI method chosen by the user.
|
||||||
|
|
||||||
|
[*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
|
||||||
|
only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
|
||||||
|
accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
function ApproveListing(request){
|
||||||
|
console.log("In js approve listing");
|
||||||
|
console.log(request.accounts[3].Address)
|
||||||
|
console.log(request.meta.Remote)
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApproveTx(request){
|
||||||
|
console.log("test");
|
||||||
|
console.log("from");
|
||||||
|
return "Reject";
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(thing){
|
||||||
|
console.log(thing.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func hexAddr(a string) common.Address { return common.BytesToAddress(common.Hex2Bytes(a)) }
|
||||||
|
func mixAddr(a string) (*common.MixedcaseAddress, error) {
|
||||||
|
return common.NewMixedcaseAddressFromString(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRuleEngine(js string) (*rulesetUi, error) {
|
||||||
|
r, err := NewRuleEvaluator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to create js engine: %v", err)
|
||||||
|
}
|
||||||
|
if err = r.Init(js); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to load bootstrap js: %v", err)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListRequest(t *testing.T) {
|
||||||
|
accs := make([]signer.Account, 5)
|
||||||
|
|
||||||
|
for i, _ := range accs {
|
||||||
|
addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i)
|
||||||
|
acc := signer.Account{
|
||||||
|
Address: common.BytesToAddress(common.Hex2Bytes(addr)),
|
||||||
|
URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)},
|
||||||
|
}
|
||||||
|
accs[i] = acc
|
||||||
|
}
|
||||||
|
|
||||||
|
js := `function ApproveListing(accounts, meta){ return "Approve" }`
|
||||||
|
|
||||||
|
r, err := initRuleEngine(js)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create evaluator %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := r.ApproveListing(&signer.ListRequest{
|
||||||
|
accs,
|
||||||
|
signer.Metadata{
|
||||||
|
"remoteip", "localip", "inproc",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if len(resp.Accounts) != len(accs) {
|
||||||
|
t.Errorf("Expected check to resolve to 'Approve'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignTxRequest(t *testing.T) {
|
||||||
|
|
||||||
|
js := `
|
||||||
|
function ApproveTx(jsonstr){
|
||||||
|
console.log(jsonstr)
|
||||||
|
r = JSON.parse(jsonstr)
|
||||||
|
console.log("from", r.from)
|
||||||
|
console.log("transaction.to", r.transaction.to);
|
||||||
|
console.log("transaction.value", r.transaction.value);
|
||||||
|
console.log("transaction.nonce", r.transaction.nonce);
|
||||||
|
if(r.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
|
||||||
|
if(r.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
|
||||||
|
}`
|
||||||
|
|
||||||
|
r, err := initRuleEngine(js)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create evaluator %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to, err := mixAddr("000000000000000000000000000000000000dead")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
from, err := mixAddr("0000000000000000000000000000000000001337")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("to %v", to.Address().String())
|
||||||
|
resp, err := r.ApproveTx(&signer.SignTxRequest{
|
||||||
|
Transaction: signer.TransactionArg{To: to},
|
||||||
|
From: *from,
|
||||||
|
Callinfo: "",
|
||||||
|
Meta: signer.Metadata{"remoteip", "localip", "inproc"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if !resp.Approved {
|
||||||
|
t.Errorf("Expected check to resolve to 'Approve'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingFunc(t *testing.T) {
|
||||||
|
r, err := initRuleEngine(JS)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create evaluator %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.vm.Call("MissingMethod", nil, "test")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.checkApproval("MissingMethod", nil, nil) == nil {
|
||||||
|
t.Errorf("Expected error to resolve to 'Reject'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Err %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestStorage(t *testing.T) {
|
||||||
|
|
||||||
|
js := `
|
||||||
|
function testStorage(){
|
||||||
|
storage.Put("mykey", "myvalue")
|
||||||
|
a = storage.Get("mykey")
|
||||||
|
|
||||||
|
storage.Put("mykey", ["a", "list"]) // Should result in "a,list"
|
||||||
|
a += storage.Get("mykey")
|
||||||
|
|
||||||
|
|
||||||
|
storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]"
|
||||||
|
a += storage.Get("mykey")
|
||||||
|
|
||||||
|
|
||||||
|
storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
|
||||||
|
a += storage.Get("mykey")
|
||||||
|
|
||||||
|
a += storage.Get("missingkey") //Missing keys should result in empty string
|
||||||
|
storage.Put("","missing key==noop") // Can't store with 0-length key
|
||||||
|
a += storage.Get("") // Should result in ''
|
||||||
|
|
||||||
|
var b = new BigNumber(2)
|
||||||
|
var c = new BigNumber(16)//"0xf0",16)
|
||||||
|
var d = b.plus(c)
|
||||||
|
console.log(d)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
`
|
||||||
|
r, err := initRuleEngine(js)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create evaluator %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := r.vm.Call("testStorage", nil, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retval, err := v.ToString()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
exp := `myvaluea,list[object Object]{"an":"object"}`
|
||||||
|
if retval != exp {
|
||||||
|
t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval)
|
||||||
|
}
|
||||||
|
fmt.Printf("Err %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExampleTxWindow = `
|
||||||
|
function big(str){
|
||||||
|
if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
|
||||||
|
return new BigNumber(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time window: 1 week
|
||||||
|
var window = 1000* 3600*24*7;
|
||||||
|
|
||||||
|
// Limit : 1 ether
|
||||||
|
var limit = new BigNumber("1e18");
|
||||||
|
|
||||||
|
function isLimitOk(transaction){
|
||||||
|
var value = big(transaction.value)
|
||||||
|
// Start of our window function
|
||||||
|
var windowstart = new Date().getTime() - window;
|
||||||
|
|
||||||
|
var txs = [];
|
||||||
|
var stored = storage.Get('txs');
|
||||||
|
|
||||||
|
if(stored != ""){
|
||||||
|
txs = JSON.parse(stored)
|
||||||
|
}
|
||||||
|
// First, remove all that have passed out of the time-window
|
||||||
|
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
|
||||||
|
console.log(txs, newtxs.length);
|
||||||
|
|
||||||
|
// Secondly, aggregate the current sum
|
||||||
|
sum = new BigNumber(0)
|
||||||
|
|
||||||
|
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
|
||||||
|
console.log("Sum so far", sum);
|
||||||
|
console.log("Requested", value.toNumber());
|
||||||
|
// Would we exceed weekly limit ?
|
||||||
|
if (sum.plus(value).lt(limit)){
|
||||||
|
// Add this to the storage
|
||||||
|
newtxs.push({tstamp: new Date().getTime(), value: value});
|
||||||
|
storage.Put("txs", JSON.stringify(newtxs));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
function ApproveTx(jsonstr){
|
||||||
|
r = JSON.parse(jsonstr);
|
||||||
|
console.log("Requested value ", r.transaction.value)
|
||||||
|
|
||||||
|
if (isLimitOk(r.transaction)){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
return "Nope"
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func dummyTx(value *hexutil.Big) *signer.SignTxRequest {
|
||||||
|
|
||||||
|
to, _ := mixAddr("000000000000000000000000000000000000dead")
|
||||||
|
from, _ := mixAddr("000000000000000000000000000000000000dead")
|
||||||
|
|
||||||
|
return &signer.SignTxRequest{
|
||||||
|
Transaction: signer.TransactionArg{
|
||||||
|
To: to,
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
From: *from,
|
||||||
|
Callinfo: "Warning, all your base are bellong to us",
|
||||||
|
Meta: signer.Metadata{"remoteip", "localip", "inproc"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLimitWindow(t *testing.T) {
|
||||||
|
|
||||||
|
r, err := initRuleEngine(ExampleTxWindow)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create evaluator %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 0.3 ether: 429D069189E0000 wei
|
||||||
|
v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000"))
|
||||||
|
h := hexutil.Big(*v)
|
||||||
|
// The first three should succeed
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
resp, err := r.ApproveTx(dummyTx(&h))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if !resp.Approved {
|
||||||
|
t.Errorf("Expected check to resolve to 'Approve'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fourth should fail
|
||||||
|
resp, err := r.ApproveTx(dummyTx(&h))
|
||||||
|
if resp.Approved {
|
||||||
|
t.Errorf("Expected check to resolve to 'Reject'")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
cmd/signer/storage/storage.go
Normal file
76
cmd/signer/storage/storage.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage interface {
|
||||||
|
// Put stores a value by key. 0-length keys results in no-op
|
||||||
|
Put(key, value string)
|
||||||
|
// Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length
|
||||||
|
Get(key string) string
|
||||||
|
// New creates a new (sub) namespace for the storage
|
||||||
|
New(namespace string) Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// EphemeralStorage is an in-memory storage that does
|
||||||
|
// not persist values to disk. Mainly used for testing
|
||||||
|
type EphemeralStorage struct {
|
||||||
|
data map[string]string
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EphemeralStorage) Put(key, value string) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key = fmt.Sprintf("%s.%s", s.namespace, key)
|
||||||
|
fmt.Printf("storage: put %v -> %v\n", key, value)
|
||||||
|
s.data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EphemeralStorage) Get(key string) string {
|
||||||
|
|
||||||
|
if len(key) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
key = fmt.Sprintf("%s.%s", s.namespace, key)
|
||||||
|
fmt.Printf("storage: get %v\n", key)
|
||||||
|
if v, exist := s.data[key]; exist {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EphemeralStorage) New(namespace string) Storage {
|
||||||
|
child := &EphemeralStorage{
|
||||||
|
data: make(map[string]string),
|
||||||
|
namespace: fmt.Sprintf("%s.%s", namespace),
|
||||||
|
}
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEphemeralStorage() Storage {
|
||||||
|
s := &EphemeralStorage{
|
||||||
|
data: make(map[string]string),
|
||||||
|
namespace: "root",
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue