cmd: add support for `data` signing

pull/295/head
MaxMustermann2 2 years ago
parent 5d91d48bba
commit 5cbf0c66dc
No known key found for this signature in database
GPG Key ID: 4F4AB9DB6FF24C94
  1. 81
      cmd/subcommands/ethtransfer.go
  2. 11
      cmd/subcommands/transfer.go
  3. 49
      pkg/console/console.go
  4. 1
      pkg/transaction/controller.go
  5. 55
      pkg/transaction/ethcontroller.go
  6. 17
      pkg/transaction/util.go

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
@ -43,9 +44,13 @@ func ethHandlerForShard(node string) (*rpc.HTTPMessenger, error) {
// Note that the vars need to be set before calling this handler.
func ethHandlerForTransaction(txLog *transactionLog) error {
from := fromAddress.String()
networkHandler, err := ethHandlerForShard(node)
if handlerForError(txLog, err) != nil {
return err
var networkHandler *rpc.HTTPMessenger
if !offlineSign {
var err error
networkHandler, err = ethHandlerForShard(node)
if handlerForError(txLog, err) != nil {
return err
}
}
var ctrlr *transaction.EthController
@ -98,12 +103,17 @@ func ethHandlerForTransaction(txLog *transactionLog) error {
gLimit = uint64(tempLimit)
}
dataByte, err := transaction.StringToByte(data)
if err != nil {
return handlerForError(txLog, err)
}
txLog.TimeSigned = time.Now().UTC().Format(timeFormat) // Approximate time of signature
err = ctrlr.ExecuteEthTransaction(
nonce, gLimit,
toAddress.String(),
amt, gPrice,
[]byte{},
dataByte,
)
if dryRun {
@ -187,6 +197,9 @@ func ethOpts(ctlr *transaction.EthController) {
if dryRun {
ctlr.Behavior.DryRun = true
}
if offlineSign {
ctlr.Behavior.OfflineSign = true
}
if useLedgerWallet {
ctlr.Behavior.SigningImpl = transaction.Ledger
}
@ -204,6 +217,9 @@ func init() {
Create an Ethereum compatible transaction, sign it, and send off to the Harmony blockchain
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if offlineSign {
dryRun = true
}
if givenFilePath == "" {
for _, flagName := range [...]string{"from", "to", "amount", "chain-id"} {
_ = cmd.MarkFlagRequired(flagName)
@ -269,15 +285,72 @@ Create an Ethereum compatible transaction, sign it, and send off to the Harmony
cmdEthTransfer.Flags().Var(&fromAddress, "from", "sender's one address, keystore must exist locally")
cmdEthTransfer.Flags().Var(&toAddress, "to", "the destination one address")
cmdEthTransfer.Flags().BoolVar(&dryRun, "dry-run", false, "do not send signed transaction")
cmdEthTransfer.Flags().BoolVar(&offlineSign, "offline-sign", false, "output offline signing")
cmdEthTransfer.Flags().BoolVar(&trueNonce, "true-nonce", false, "send transaction with on-chain nonce")
cmdEthTransfer.Flags().StringVar(&amount, "amount", "0", "amount to send (ONE)")
cmdEthTransfer.Flags().StringVar(&gasPrice, "gas-price", "100", "gas price to pay (NANO)")
cmdEthTransfer.Flags().StringVar(&gasLimit, "gas-limit", "", "gas limit")
cmdEthTransfer.Flags().StringVar(&inputNonce, "nonce", "", "set nonce for tx")
cmdEthTransfer.Flags().StringVar(&data, "data", "", "transaction data")
cmdEthTransfer.Flags().StringVar(&targetChain, "chain-id", "", "what chain ID to target")
cmdEthTransfer.Flags().Uint32Var(&timeout, "timeout", defaultTimeout, "set timeout in seconds. Set to 0 to not wait for confirm")
cmdEthTransfer.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt)
cmdEthTransfer.Flags().StringVar(&passphraseFilePath, "passphrase-file", "", "path to a file containing the passphrase")
RootCmd.AddCommand(cmdEthTransfer)
cmdOfflineSignEthTransfer := &cobra.Command{
Use: "offline-sign-eth-transfer",
Short: "Send a Offline Signed Ethereum transaction",
Args: cobra.ExactArgs(0),
Long: `
Send a offline signed transaction to the Harmony blockchain (on the same shard)
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if givenFilePath == "" {
return fmt.Errorf("must give a offline-signed file")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
var txLogs []*transactionLog
networkHandler := rpc.NewHTTPHandler(node)
openFile, err := os.Open(givenFilePath)
if err != nil {
return err
}
defer openFile.Close()
err = json.NewDecoder(openFile).Decode(&txLogs)
if err != nil {
return err
}
for _, txLog := range txLogs {
if len(txLog.Errors) > 0 {
continue
}
ctrlr := transaction.NewEthController(networkHandler, nil, nil, *chainName.chainID, ethOpts)
err := ctrlr.ExecuteRawTransaction(txLog.RawTxn)
if handlerForError(txLog, err) != nil {
txLog.Errors = append(txLog.Errors, err.Error())
continue
}
if txHash := ctrlr.TransactionHash(); txHash != nil {
txLog.TxHash = *txHash
}
txLog.Receipt = ctrlr.Receipt()["result"]
}
fmt.Println(common.ToJSONUnsafe(txLogs, true))
return nil
},
}
RootCmd.AddCommand(cmdOfflineSignEthTransfer)
}

@ -42,6 +42,7 @@ var (
transferFileFlags []transferFlags
timeout uint32
timeFormat = "2006-01-02 15:04:05.000000"
data string
)
type transactionLog struct {
@ -169,6 +170,11 @@ func handlerForTransaction(txLog *transactionLog) error {
gLimit = uint64(tempLimit)
}
dataByte, err := transaction.StringToByte(data)
if err != nil {
return handlerForError(txLog, err)
}
addr := toAddress.String()
txLog.TimeSigned = time.Now().UTC().Format(timeFormat) // Approximate time of signature
@ -177,7 +183,7 @@ func handlerForTransaction(txLog *transactionLog) error {
&addr,
fromShardID, toShardID,
amt, gPrice,
[]byte{},
dataByte,
)
if dryRun {
@ -414,6 +420,7 @@ Create a transaction, sign it, and send off to the Harmony blockchain
cmdTransfer.Flags().StringVar(&inputNonce, "nonce", "", "set nonce for tx")
cmdTransfer.Flags().Uint32Var(&fromShardID, "from-shard", 0, "source shard id")
cmdTransfer.Flags().Uint32Var(&toShardID, "to-shard", 0, "target shard id")
cmdTransfer.Flags().StringVar(&data, "data", "", "transaction data")
cmdTransfer.Flags().StringVar(&targetChain, "chain-id", "", "what chain ID to target")
cmdTransfer.Flags().Uint32Var(&timeout, "timeout", defaultTimeout, "set timeout in seconds. Set to 0 to not wait for confirm")
cmdTransfer.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt)
@ -449,7 +456,7 @@ Get Nonce From a Account
Short: "Send a Offline Signed transaction",
Args: cobra.ExactArgs(0),
Long: `
Send a offline signed to the Harmony blockchain
Send a offline signed transaction to the Harmony blockchain
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if givenFilePath == "" {

@ -4,18 +4,6 @@ import (
"encoding/hex"
"errors"
"fmt"
ethereum_rpc "github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/go-sdk/pkg/account"
"github.com/harmony-one/go-sdk/pkg/address"
"github.com/harmony-one/go-sdk/pkg/common"
"github.com/harmony-one/go-sdk/pkg/console/jsre"
"github.com/harmony-one/go-sdk/pkg/console/jsre/deps"
"github.com/harmony-one/go-sdk/pkg/console/prompt"
"github.com/harmony-one/go-sdk/pkg/console/web3ext"
"github.com/harmony-one/go-sdk/pkg/rpc"
"github.com/harmony-one/go-sdk/pkg/store"
"github.com/harmony-one/go-sdk/pkg/transaction"
"github.com/harmony-one/harmony/accounts"
"io"
"io/ioutil"
"math/big"
@ -29,6 +17,19 @@ import (
"syscall"
"time"
ethereum_rpc "github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/go-sdk/pkg/account"
"github.com/harmony-one/go-sdk/pkg/address"
"github.com/harmony-one/go-sdk/pkg/common"
"github.com/harmony-one/go-sdk/pkg/console/jsre"
"github.com/harmony-one/go-sdk/pkg/console/jsre/deps"
"github.com/harmony-one/go-sdk/pkg/console/prompt"
"github.com/harmony-one/go-sdk/pkg/console/web3ext"
"github.com/harmony-one/go-sdk/pkg/rpc"
"github.com/harmony-one/go-sdk/pkg/store"
"github.com/harmony-one/go-sdk/pkg/transaction"
"github.com/harmony-one/harmony/accounts"
"github.com/dop251/goja"
"github.com/mattn/go-colorable"
"github.com/peterh/liner"
@ -544,6 +545,18 @@ func (b *bridge) HmyGetListAccounts(call jsre.Call) (goja.Value, error) {
return call.VM.ToValue(accounts), nil
}
func getTxData(txObj *goja.Object) ([]byte, error) {
dataObj := txObj.Get("data")
if dataObj != nil {
dataStr := dataObj.Export().(string)
if !strings.HasPrefix(dataStr, "0x") {
return nil, fmt.Errorf("Invalid data literal: %q", dataStr)
}
return hex.DecodeString(dataStr[2:])
}
return nil, nil
}
func (b *bridge) HmySignTransaction(call jsre.Call) (goja.Value, error) {
txObj := call.Arguments[0].ToObject(call.VM)
password := call.Arguments[1].String()
@ -553,6 +566,10 @@ func (b *bridge) HmySignTransaction(call jsre.Call) (goja.Value, error) {
gasLimit := getStringFromJsObjWithDefault(txObj, "gas", "1000000")
amount := getStringFromJsObjWithDefault(txObj, "value", "0")
gasPrice := getStringFromJsObjWithDefault(txObj, "gasPrice", "1")
input, err := transaction.StringToByte(getStringFromJsObjWithDefault(txObj, "data", ""))
if err != nil {
return nil, err
}
networkHandler := rpc.NewHTTPHandler(b.console.nodeUrl)
chanId, err := common.StringToChainID(b.console.net)
@ -598,7 +615,7 @@ func (b *bridge) HmySignTransaction(call jsre.Call) (goja.Value, error) {
toP,
uint32(b.console.shardId), uint32(b.console.shardId),
amt, gPrice,
[]byte{},
input,
)
if err != nil {
return nil, err
@ -635,6 +652,10 @@ func (b *bridge) HmySendTransaction(call jsre.Call) (goja.Value, error) {
gasLimit := getStringFromJsObjWithDefault(txObj, "gas", "1000000")
amount := getStringFromJsObjWithDefault(txObj, "value", "0")
gasPrice := getStringFromJsObjWithDefault(txObj, "gasPrice", "1")
input, err := transaction.StringToByte(getStringFromJsObjWithDefault(txObj, "data", ""))
if err != nil {
return nil, err
}
networkHandler := rpc.NewHTTPHandler(b.console.nodeUrl)
chanId, err := common.StringToChainID(b.console.net)
@ -680,7 +701,7 @@ func (b *bridge) HmySendTransaction(call jsre.Call) (goja.Value, error) {
toP,
uint32(b.console.shardId), uint32(b.console.shardId),
amt, gPrice,
[]byte{},
input,
)
if err != nil {
return nil, err

@ -205,7 +205,6 @@ func (C *Controller) setAmount(amount numeric.Dec) {
return
}
}
C.transactionForRPC.params["transfer-amount"] = amountInAtto
}

@ -134,34 +134,37 @@ func (C *EthController) setAmount(amount numeric.Dec) {
})
return
}
balanceRPCReply, err := C.messenger.SendRPC(
rpc.Method.GetBalance,
p{address.ToBech32(C.sender.account.Address), "latest"},
)
if err != nil {
C.executionError = err
return
}
currentBalance, _ := balanceRPCReply["result"].(string)
bal, _ := new(big.Int).SetString(currentBalance[2:], 16)
balance := numeric.NewDecFromBigInt(bal)
gasAsDec := C.transactionForRPC.params["gas-price"].(numeric.Dec)
gasAsDec = gasAsDec.Mul(numeric.NewDec(int64(C.transactionForRPC.params["gas-limit"].(uint64))))
amountInAtto := amount.Mul(oneAsDec)
total := amountInAtto.Add(gasAsDec)
if total.GT(balance) {
balanceInOne := balance.Quo(oneAsDec)
C.executionError = ErrBadTransactionParam
errorMsg := fmt.Sprintf(
"insufficient balance of %s in shard %d for the requested transfer of %s",
balanceInOne.String(), C.transactionForRPC.params["from-shard"].(uint32), amount.String(),
if !C.Behavior.OfflineSign {
balanceRPCReply, err := C.messenger.SendRPC(
rpc.Method.GetBalance,
p{address.ToBech32(C.sender.account.Address), "latest"},
)
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
return
if err != nil {
C.executionError = err
return
}
currentBalance, _ := balanceRPCReply["result"].(string)
bal, _ := new(big.Int).SetString(currentBalance[2:], 16)
balance := numeric.NewDecFromBigInt(bal)
if total.GT(balance) {
balanceInOne := balance.Quo(oneAsDec)
C.executionError = ErrBadTransactionParam
errorMsg := fmt.Sprintf(
"insufficient balance of %s in shard %d for the requested transfer of %s",
balanceInOne.String(), C.transactionForRPC.params["from-shard"].(uint32), amount.String(),
)
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
return
}
}
C.transactionForRPC.params["transfer-amount"] = amountInAtto
}
@ -304,4 +307,10 @@ func (C *EthController) ExecuteEthTransaction(
return C.executionError
}
// TODO: add logic to create staking transactions in the SDK.
func (C *EthController) ExecuteRawTransaction(txn string) error {
C.transactionForRPC.signature = &txn
C.sendSignedTx()
C.txConfirmation()
return C.executionError
}

@ -0,0 +1,17 @@
package transaction
import (
"encoding/hex"
"fmt"
"strings"
)
func StringToByte(dataStr string) ([]byte, error) {
if len(dataStr) == 0 {
return []byte{}, nil
}
if !strings.HasPrefix(dataStr, "0x") {
return nil, fmt.Errorf("invalid data literal: %q", dataStr)
}
return hex.DecodeString(dataStr[2:])
}
Loading…
Cancel
Save