mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
signer: implement OnApprovedTx, change signing response (API BREAKAGE)
This commit is contained in:
parent
da312a1dcb
commit
a8e68b9cc3
9 changed files with 280 additions and 131 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue