signer: implement OnApprovedTx, change signing response (API BREAKAGE)

This commit is contained in:
Martin Holst Swende 2018-02-15 00:33:16 +01:00
parent da312a1dcb
commit a8e68b9cc3
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
9 changed files with 280 additions and 131 deletions

View file

@ -33,8 +33,8 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet"
"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/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
@ -46,7 +46,7 @@ type ExternalAPI interface {
// New request to create a new account
New(ctx context.Context) (accounts.Account, error)
// SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error)
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// EcRecover - request to perform ecrecover
@ -77,6 +77,9 @@ type SignerUI interface {
ShowError(message string)
// ShowInfo displays info message to user
ShowInfo(message string)
// OnApprovedTx notifies the UI about a transaction having been successfully signed.
// This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient.
OnApprovedTx(tx ethapi.SignTransactionResult)
}
// SignerAPI defines the actual implementation of ExternalAPI
@ -123,18 +126,16 @@ func (m Metadata) String() string {
type (
// SignTxRequest contains info about a Transaction to sign
SignTxRequest struct {
Transaction TransactionArg `json:"transaction"`
From common.MixedcaseAddress `json:"from"`
Callinfo string `json:"call_info"`
Meta Metadata `json:"meta"`
Transaction SendTxArgs `json:"transaction"`
Callinfo string `json:"call_info"`
Meta Metadata `json:"meta"`
}
// SignTxResponse result from SignTxRequest
SignTxResponse struct {
//The UI may make changes to the TX
Transaction TransactionArg `json:"transaction"`
From common.MixedcaseAddress `json:"from"`
Approved bool `json:"approved"`
Password string `json:"password"`
Transaction SendTxArgs `json:"transaction"`
Approved bool `json:"approved"`
Password string `json:"password"`
}
// ExportRequest info about query to export accounts
ExportRequest struct {
@ -271,23 +272,17 @@ func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
return be[0].(*keystore.KeyStore).NewAccount(resp.Password)
}
func toTransaction(args *TransactionArg) *types.Transaction {
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
} else {
return types.NewTransaction(uint64(*args.Nonce), args.To.Address(), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
}
}
// logDiff logs the difference between the incoming (original) transaction and the one returned from the signer.
// it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow
// UI-modifications to requests
func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
modified := false
if f0, f1 := original.From, new.From; f0 != f1 {
modified = true
if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) {
log.Info("Sender-account changed by UI", "was", f0, "is", f1)
modified = true
}
if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) {
log.Info("Recipient-account changed by UI", "was", t0, "is", t1)
modified = true
@ -310,9 +305,19 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
log.Info("Value changed by UI", "was", v0, "is", v1)
}
}
if d0, d1 := original.Transaction.Data, new.Transaction.Data; !bytes.Equal(d0, d1) {
modified = true
log.Info("Data changed by UI", "was", common.ToHex(d0), "is", common.ToHex(d1))
if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 {
d0s := ""
d1s := ""
if d0 != nil {
d0s = common.ToHex(*d0)
}
if d1 != nil {
d1s = common.ToHex(*d1)
}
if d1s != d0s {
modified = true
log.Info("Data changed by UI", "was", d0s, "is", d1s)
}
}
if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 {
@ -324,41 +329,68 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
return modified
}
// SignTransaction signs the given Transaction and returns it in an RLP encoded form
// that can be posted to `eth_sendRawTransaction`.
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) {
// determineCallInfo turns ABI-data + methodselector (if given) into a string suitable
// to present to the user.
func (api *SignerAPI) determineCallInfo(data []byte, methodSelector *string) string {
if len(data) < 4 {
return ""
}
var (
err error
result SignTxResponse
selector string
err error
)
req := SignTxRequest{Transaction: args, From: from, Meta: MetadataFromContext(ctx)}
data := args.Data
if len(data) > 3 {
// Try to make sense of the data
var selector string
if methodSelector == nil {
selector, err = api.abidb.LookupMethodSelector(data[:4])
if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err}.String()
}
} else {
selector = *methodSelector
// Try to make sense of the data
if methodSelector == nil {
selector, err = api.abidb.LookupMethodSelector(data[:4])
if err != nil {
return errorWrapper{"Warning! Could not locate ABI", err}.String()
}
if selector != "" {
abidata, err := MethodSelectorToAbi(selector)
} else {
selector = *methodSelector
}
if selector != "" {
abiData, err := MethodSelectorToAbi(selector)
if err != nil {
return errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else {
var info *decodedCallData
info, err = parseCallData(data, string(abiData))
if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
return errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else {
var info *decodedCallData
info, err = parseCallData(data, string(abidata))
if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else {
req.Callinfo = info.String()
}
return info.String()
}
}
}
return ""
}
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
var (
err error
result SignTxResponse
data []byte
)
// Prevent accidental erroneous usage of both 'input' and 'data'
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return nil, errors.New(`Ambiguous request: moth "data" and "input" are set and are not identical`)
}
if args.Data != nil {
data = *args.Data
} else if args.Input != nil {
data = *args.Input
*args.Data = data
*args.Input = nil
}
req := SignTxRequest{
Transaction: args,
Meta: MetadataFromContext(ctx),
Callinfo:api.determineCallInfo(data, methodSelector),
}
// Process approval
result, err = api.ui.ApproveTx(&req)
if err != nil {
return nil, err
@ -368,25 +400,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Mixedcase
}
// Log changes made by the UI to the signing-request
logDiff(&req, &result)
var (
acc accounts.Account
wallet accounts.Wallet
)
acc = accounts.Account{Address: result.From.Address()}
acc = accounts.Account{Address: result.Transaction.From.Address()}
wallet, err = api.am.Find(acc)
if err != nil {
return nil, err
}
var tx = toTransaction(&result.Transaction)
// Convert fields into a real transaction
var unsignedTx = result.Transaction.toTransaction()
// The one to sign is the one that was returned from the UI
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, tx, api.chainID)
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID)
if err != nil {
api.ui.ShowError(err.Error())
return nil, err
}
return rlp.EncodeToBytes(signedTx)
rlpdata, err := rlp.EncodeToBytes(signedTx)
response := ethapi.SignTransactionResult{rlpdata, signedTx}
// Finally, send the signed tx to the UI
api.ui.OnApprovedTx(response)
// ...and to the external caller
return &response, nil
}
// Sign calculates an Ethereum ECDSA signature for:

View file

@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/internal/ethapi"
)
//Used for testing
@ -24,18 +25,22 @@ type HeadlessUI struct {
controller chan string
}
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
fmt.Printf("OnApproved called")
}
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
switch <-ui.controller {
case "Y":
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil
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, request.From, true, <-ui.controller}, nil
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
default:
return SignTxResponse{request.Transaction, request.From, false, ""}, nil
return SignTxResponse{request.Transaction, false, ""}, nil
}
}
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
@ -231,19 +236,21 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(h))
}
}
func mkTestTx() TransactionArg {
func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := (*hexutil.Big)(big.NewInt(21000))
gasPrice := (*hexutil.Big)(big.NewInt(2000000000))
value := (*hexutil.Big)(big.NewInt(1e18))
nonce := (hexutil.Uint64)(0)
tx := TransactionArg{
&to,
gas,
gasPrice,
value,
common.Hex2Bytes("01020304050607080a"),
&nonce}
data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
tx := SendTxArgs{
From:from,
To: &to,
Gas: gas,
GasPrice: gasPrice,
Value: value,
Data: &data,
Nonce: &nonce}
return tx
}
@ -251,8 +258,7 @@ func TestSignTx(t *testing.T) {
var (
list Accounts
h []byte
h2 []byte
res, res2 *ethapi.SignTransactionResult
err error
)
@ -266,22 +272,22 @@ func TestSignTx(t *testing.T) {
a := common.NewMixedcaseAddress(list[0].Address)
methodSig := "test(uint)"
tx := mkTestTx()
tx := mkTestTx(a)
control <- "Y"
control <- "wrongpassword"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
if h != nil {
t.Errorf("Expected nil-data, got %h", h)
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"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
if h != nil {
t.Errorf("Expected nil-data, got %h", h)
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)
@ -289,13 +295,13 @@ func TestSignTx(t *testing.T) {
control <- "Y"
control <- "apassword"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil {
t.Fatal(err)
}
parsedTx := &types.Transaction{}
rlp.Decode(bytes.NewReader(h), parsedTx)
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())
@ -303,11 +309,11 @@ func TestSignTx(t *testing.T) {
control <- "Y"
control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(h, h2) {
if !bytes.Equal(res.Raw, res2.Raw) {
t.Error("Expected tx to be unmodified by UI")
}
@ -315,19 +321,19 @@ func TestSignTx(t *testing.T) {
control <- "M"
control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil {
t.Fatal(err)
}
parsedTx2 := &types.Transaction{}
rlp.Decode(bytes.NewReader(h), parsedTx2)
//The tx should NOT be modified by the UI
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 changed, got %v", parsedTx.Value())
t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
}
if bytes.Equal(h, h2) {
if bytes.Equal(res.Raw, res2.Raw) {
t.Error("Expected tx to be modified by UI")
}

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/internal/ethapi"
)
type AuditLogger struct {
@ -41,15 +42,17 @@ func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) {
return l.api.New(ctx)
}
func (l *AuditLogger) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) {
func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"from", from.String(), "tx", args.String(),
"tx", args.String(),
"methodSelector", methodSelector)
b, e := l.api.SignTransaction(ctx, from, args, methodSelector)
l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
res, e := l.api.SignTransaction(ctx, args, methodSelector)
if res != nil{
l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(res.Raw), "error", e)
}else{
l.log.Info("SignTransaction", "type", "response", "data", res, "error", e)
}
return res, e
}
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {

View file

@ -24,6 +24,7 @@ import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/ssh/terminal"
)
@ -108,10 +109,13 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
} else {
fmt.Printf("to: <contact creation>\n")
}
fmt.Printf("from: %v\n", request.From.String())
fmt.Printf("from: %v\n", request.Transaction.From.String())
fmt.Printf("value: %v wei\n", weival)
if len(request.Transaction.Data) > 0 {
fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data))
if request.Transaction.Data != nil{
d := *request.Transaction.Data
if len(d) > 0 {
fmt.Printf("data: %v\n", common.Bytes2Hex(d))
}
}
if request.Callinfo != "" {
fmt.Printf("\nNote: This Transaction contains data. Review abi-decoding info below:")
@ -122,9 +126,9 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
showMetadata(request.Meta)
fmt.Printf("-------------------------------------------\n")
if !ui.confirm() {
return SignTxResponse{request.Transaction, request.From, false, ""}, nil
return SignTxResponse{request.Transaction, false, ""}, nil
}
return SignTxResponse{request.Transaction, request.From, true, ui.readPassword()}, nil
return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil
}
// ApproveSignData prompt the user for confirmation to request to sign data
@ -222,6 +226,9 @@ func (ui *CommandlineUI) ShowError(message string) {
// ShowInfo displays info message to user
func (ui *CommandlineUI) ShowInfo(message string) {
fmt.Printf("Info: %v\n", message)
}
func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
fmt.Printf("Transaction signed: %v", tx.Tx.String())
}

View file

@ -188,7 +188,7 @@ func testExternalUI(api *SignerAPI) {
}
var err error
_, err = api.SignTransaction(ctx, common.MixedcaseAddress{}, TransactionArg{}, nil)
_, err = api.SignTransaction(ctx, SendTxArgs{From:common.MixedcaseAddress{}}, nil)
checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err)

View file

@ -22,6 +22,7 @@ import (
"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/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/robertkrimen/otto"
"os"
@ -84,7 +85,7 @@ 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))
v, err := r.vm.Call(jsfunc, nil, string(jsarg))
if err != nil {
log.Info("error occurred during execution", "error", err)
@ -106,7 +107,7 @@ func (r *rulesetUi) checkApproval(jsfunc string, jsarg []byte, err error) error
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{Transaction: request.Transaction, Approved: true, Password: ""}, nil
}
return signer.SignTxResponse{Approved: false}, err
}
@ -156,3 +157,17 @@ func (r *rulesetUi) ShowInfo(message string) {
log.Info(message)
r.next.ShowInfo(message)
}
func (r *rulesetUi) OnApprovedTx(tx ethapi.SignTransactionResult) {
jsonTx, err := json.Marshal(tx)
if err != nil {
log.Warn("failed marshalling transaction", "tx", tx)
return
}
_, err = r.vm.Call("OnApprovedTx", nil, string(jsonTx))
if err != nil {
fmt.Printf("Error in onapprove %v", err)
log.Warn("error occurred during execution", "error", err)
}
}

View file

@ -6,6 +6,8 @@ import (
"github.com/ethereum/go-ethereum/cmd/signer"
"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"
"math/big"
"testing"
)
@ -75,7 +77,7 @@ func TestListRequest(t *testing.T) {
accs[i] = acc
}
js := `function ApproveListing(accounts, meta){ return "Approve" }`
js := `function ApproveListing(){ return "Approve" }`
r, err := initRuleEngine(js)
if err != nil {
@ -99,12 +101,12 @@ func TestSignTxRequest(t *testing.T) {
function ApproveTx(jsonstr){
console.log(jsonstr)
r = JSON.parse(jsonstr)
console.log("from", r.from)
console.log("transaction.from", r.transaction.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"}
if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
}`
r, err := initRuleEngine(js)
@ -125,10 +127,11 @@ func TestSignTxRequest(t *testing.T) {
}
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"},
Transaction: signer.SendTxArgs{
From: *from,
To: to},
Callinfo: "",
Meta: signer.Metadata{"remoteip", "localip", "inproc"},
})
if err != nil {
t.Errorf("Unexpected error %v", err)
@ -243,46 +246,79 @@ const ExampleTxWindow = `
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());
console.log("ApproveTx > Sum so far", sum);
console.log("ApproveTx > 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;
return sum.plus(value).lt(limit)
}
function ApproveTx(jsonstr){
r = JSON.parse(jsonstr);
console.log("Requested value ", r.transaction.value)
var r = JSON.parse(jsonstr)
if (isLimitOk(r.transaction)){
return "Approve"
}
return "Nope"
}
/**
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
* 'response_str' contains the return value that will be sent to the external caller.
* The return value from this method is ignore - the reason for having this callback is to allow the
* ruleset to keep track of approved transactions.
*
* When implementing rate-limited rules, this callback should be used.
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
* then accepts the transaction, this method will be called.
*
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
*/
function OnApprovedTx(response_str){
console.log("OnApprovedTx > called with data\n\t "+response_str)
var resp = JSON.parse(response_str)
var value = big(resp.tx.value)
var txs = []
// Load stored transactions
var stored = storage.Get('txs');
if(stored != ""){
txs = JSON.parse(stored)
}
// Add this to the storage
txs.push({tstamp: new Date().getTime(), value: value});
storage.Put("txs", JSON.stringify(txs));
}
`
func dummyTx(value *hexutil.Big) *signer.SignTxRequest {
to, _ := mixAddr("000000000000000000000000000000000000dead")
from, _ := mixAddr("000000000000000000000000000000000000dead")
n := hexutil.Uint64(3)
gas := hexutil.Big(*big.NewInt(21000))
gasPrice := hexutil.Big(*big.NewInt(2000000))
return &signer.SignTxRequest{
Transaction: signer.TransactionArg{
To: to,
Value: value,
Transaction: signer.SendTxArgs{
From: *from,
To: to,
Value: value,
Nonce: &n,
GasPrice: &gas,
Gas: &gasPrice,
},
From: *from,
Callinfo: "Warning, all your base are bellong to us",
Meta: signer.Metadata{"remoteip", "localip", "inproc"},
}
}
func dummySigned(value *big.Int) *types.Transaction {
to := common.HexToAddress("000000000000000000000000000000000000dead")
gas := big.NewInt(21000)
gasPrice := big.NewInt(2000000)
data := make([]byte, 0)
return types.NewTransaction(3, to, value, gas, gasPrice, data)
}
func TestLimitWindow(t *testing.T) {
r, err := initRuleEngine(ExampleTxWindow)
@ -299,13 +335,21 @@ func TestLimitWindow(t *testing.T) {
h := hexutil.Big(*v)
// The first three should succeed
for i := 0; i < 3; i++ {
resp, err := r.ApproveTx(dummyTx(&h))
unsigned := dummyTx(&h)
resp, err := r.ApproveTx(unsigned)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if !resp.Approved {
t.Errorf("Expected check to resolve to 'Approve'")
}
// Create a dummy signed transaction
response := ethapi.SignTransactionResult{
Tx: dummySigned(v),
Raw: common.Hex2Bytes("deadbeef"),
}
r.OnApprovedTx(response)
}
// Fourth should fail
resp, err := r.ApproveTx(dummyTx(&h))

View file

@ -18,13 +18,12 @@
package signer
import (
"github.com/ethereum/go-ethereum/log"
"context"
"sync"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"context"
)
type StdIOUI struct {
@ -114,3 +113,9 @@ func (ui *StdIOUI) ShowInfo(message string) {
log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message)
}
}
func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
err := ui.dispatch("OnApprovedTx", tx, nil)
if err != nil {
log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx)
}
}

View file

@ -23,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
)
type Accounts []Account
@ -48,7 +50,7 @@ func (a Account) String() string {
}
return err.Error()
}
/*
// TransactionArg represents a Transaction for the signer.
type TransactionArg struct {
To *common.MixedcaseAddress `json:"to"`
@ -58,11 +60,38 @@ type TransactionArg struct {
Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"`
}
*/
func (t TransactionArg) String() string {
// SendTxArgs represents the arguments to submit a transaction
type SendTxArgs struct {
From common.MixedcaseAddress `json:"from"`
To *common.MixedcaseAddress `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons.
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
}
func (t SendTxArgs) String() string {
s, err := json.Marshal(t)
if err == nil {
return string(s)
}
return err.Error()
}
func (args *SendTxArgs) toTransaction() *types.Transaction {
var input []byte
if args.Data != nil {
input = *args.Data
} else if args.Input != nil {
input = *args.Input
}
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), input)
}
return types.NewTransaction(uint64(*args.Nonce), (*args.To).Address(), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), input)
}