Add support for sending ethereum txs using go-sdk

pull/240/head
Sebastian Johnsson 4 years ago
parent 8bcfe99f40
commit d14411d0ec
  1. 283
      cmd/subcommands/ethtransfer.go
  2. 2
      go.mod
  3. 2
      go.sum
  4. 4
      pkg/common/chain-id.go
  5. 8
      pkg/transaction/controller.go
  6. 307
      pkg/transaction/ethcontroller.go
  7. 17
      pkg/transaction/transaction.go

@ -0,0 +1,283 @@
package cmd
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
"time"
"github.com/harmony-one/go-sdk/pkg/address"
"github.com/harmony-one/go-sdk/pkg/common"
"github.com/harmony-one/go-sdk/pkg/rpc"
rpcEth "github.com/harmony-one/go-sdk/pkg/rpc/eth"
"github.com/harmony-one/go-sdk/pkg/store"
"github.com/harmony-one/go-sdk/pkg/transaction"
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/core"
"github.com/spf13/cobra"
)
type ethTransferFlags struct {
FromAddress *string `json:"from"`
ToAddress *string `json:"to"`
Amount *string `json:"amount"`
PassphraseString *string `json:"passphrase-string"`
PassphraseFile *string `json:"passphrase-file"`
InputNonce *string `json:"nonce"`
GasPrice *string `json:"gas-price"`
GasLimit *string `json:"gas-limit"`
StopOnError bool `json:"stop-on-error"`
TrueNonce bool `json:"true-nonce"`
}
func ethHandlerForShard(node string) (*rpc.HTTPMessenger, error) {
return rpc.NewHTTPHandler(node), nil
}
// handlerForTransaction executes a single transaction and fills out the transaction logger accordingly.
//
// 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 ctrlr *transaction.EthController
if useLedgerWallet {
account := accounts.Account{Address: address.Parse(from)}
ctrlr = transaction.NewEthController(networkHandler, nil, &account, *chainName.chainID, ethOpts)
} else {
ks, acct, err := store.UnlockedKeystore(from, passphrase)
if handlerForError(txLog, err) != nil {
return err
}
ctrlr = transaction.NewEthController(networkHandler, ks, acct, *chainName.chainID, ethOpts)
}
nonce, err := getNonce(fromAddress.String(), networkHandler)
if err != nil {
return err
}
amt, err := common.NewDecFromString(amount)
if err != nil {
amtErr := fmt.Errorf("amount %w", err)
handlerForError(txLog, amtErr)
return amtErr
}
gPrice, err := common.NewDecFromString(gasPrice)
if err != nil {
gasErr := fmt.Errorf("gas-price %w", err)
handlerForError(txLog, gasErr)
return gasErr
}
var gLimit uint64
if gasLimit == "" {
gLimit, err = core.IntrinsicGas([]byte(""), false, true, true, false)
if handlerForError(txLog, err) != nil {
return err
}
} else {
if strings.HasPrefix(gasLimit, "-") {
limitErr := fmt.Errorf("gas-limit can not be negative: %s", gasLimit)
handlerForError(txLog, limitErr)
return limitErr
}
tempLimit, e := strconv.ParseInt(gasLimit, 10, 64)
if handlerForError(txLog, e) != nil {
return e
}
gLimit = uint64(tempLimit)
}
txLog.TimeSigned = time.Now().UTC().Format(timeFormat) // Approximate time of signature
err = ctrlr.ExecuteEthTransaction(
nonce, gLimit,
toAddress.String(),
amt, gPrice,
[]byte{},
)
if dryRun {
txLog.RawTxn = ctrlr.RawTransaction()
txLog.Transaction = make(map[string]interface{})
_ = json.Unmarshal([]byte(ctrlr.EthTransactionToJSON(false)), &txLog.Transaction)
} else if txHash := ctrlr.TransactionHash(); txHash != nil {
txLog.TxHash = *txHash
}
txLog.Receipt = ctrlr.Receipt()["result"]
if err != nil {
// Report all transaction errors first...
for _, txError := range ctrlr.TransactionErrors() {
_ = handlerForError(txLog, txError.Error())
}
err = handlerForError(txLog, err)
}
if !dryRun && timeout > 0 && txLog.Receipt == nil {
err = handlerForError(txLog, errors.New("Failed to confirm transaction"))
}
return err
}
// ethHandlerForBulkTransactions checks and sets all flags for a transaction
// from the element at index of transferFileFlags, then calls handlerForTransaction.
func ethHandlerForBulkTransactions(txLog *transactionLog, index int) error {
txnFlags := transferFileFlags[index]
// Check for required fields.
if txnFlags.FromAddress == nil || txnFlags.ToAddress == nil || txnFlags.Amount == nil {
return handlerForError(txLog, errors.New("FromAddress/ToAddress/Amount are required fields"))
}
if txnFlags.FromShardID == nil || txnFlags.ToShardID == nil {
return handlerForError(txLog, errors.New("FromShardID/ToShardID are required fields"))
}
// Set required fields.
err := fromAddress.Set(*txnFlags.FromAddress)
if handlerForError(txLog, err) != nil {
return err
}
err = toAddress.Set(*txnFlags.ToAddress)
if handlerForError(txLog, err) != nil {
return err
}
amount = *txnFlags.Amount
// Set optional fields.
if txnFlags.PassphraseFile != nil {
passphraseFilePath = *txnFlags.PassphraseFile
passphrase, err = getPassphrase()
if handlerForError(txLog, err) != nil {
return err
}
} else if txnFlags.PassphraseString != nil {
passphrase = *txnFlags.PassphraseString
} else {
passphrase = common.DefaultPassphrase
}
if txnFlags.InputNonce != nil {
inputNonce = *txnFlags.InputNonce
} else {
inputNonce = "" // Reset to default for subsequent transactions
}
if txnFlags.GasPrice != nil {
gasPrice = *txnFlags.GasPrice
} else {
gasPrice = "1" // Reset to default for subsequent transactions
}
if txnFlags.GasLimit != nil {
gasLimit = *txnFlags.GasLimit
} else {
gasLimit = "" // Reset to default for subsequent transactions
}
trueNonce = txnFlags.TrueNonce
return ethHandlerForTransaction(txLog)
}
func ethOpts(ctlr *transaction.EthController) {
if dryRun {
ctlr.Behavior.DryRun = true
}
if useLedgerWallet {
ctlr.Behavior.SigningImpl = transaction.Ledger
}
if timeout > 0 {
ctlr.Behavior.ConfirmationWaitTime = timeout
}
}
func init() {
cmdEthTransfer := &cobra.Command{
Use: "eth-transfer",
Short: "Create and send an Ethereum compatible transaction",
Args: cobra.ExactArgs(0),
Long: `
Create an Ethereum compatible transaction, sign it, and send off to the Harmony blockchain
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if givenFilePath == "" {
for _, flagName := range [...]string{"from", "to", "amount", "chain-id"} {
_ = cmd.MarkFlagRequired(flagName)
}
if trueNonce && inputNonce != "" {
return fmt.Errorf("cannot specify nonce when using true on-chain nonce")
}
} else {
data, err := ioutil.ReadFile(givenFilePath)
if err != nil {
return err
}
err = json.Unmarshal(data, &transferFileFlags)
if err != nil {
return err
}
for i, batchTx := range transferFileFlags {
if batchTx.TrueNonce && batchTx.InputNonce != nil {
return fmt.Errorf("cannot specify nonce when using true on-chain nonce for transaction number %v in batch", i+1)
}
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
rpc.Method = rpcEth.Method
if givenFilePath == "" {
pp, err := getPassphrase()
if err != nil {
return err
}
passphrase = pp // needed for passphrase assignment used in handler
txLog := transactionLog{}
err = ethHandlerForTransaction(&txLog)
fmt.Println(common.ToJSONUnsafe(txLog, !noPrettyOutput))
return err
} else {
hasError := false
var txLogs []transactionLog
for i := range transferFileFlags {
var txLog transactionLog
err := ethHandlerForBulkTransactions(&txLog, i)
txLogs = append(txLogs, txLog)
if err != nil {
hasError = true
if transferFileFlags[i].StopOnError {
break
}
}
}
fmt.Println(common.ToJSONUnsafe(txLogs, true))
if hasError {
return fmt.Errorf("one or more of your transactions returned an error " +
"-- check the log for more information")
} else {
return nil
}
}
},
}
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(&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", "1", "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(&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)
}

@ -14,7 +14,7 @@ require (
github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 // indirect
github.com/gorilla/handlers v1.4.0 // indirect
github.com/harmony-one/bls v0.0.7-0.20191214005344-88c23f91a8a9
github.com/harmony-one/harmony v1.10.2-0.20210122034820-6112100ef5a1
github.com/harmony-one/harmony v1.10.2-0.20210123081216-6993b9ad0ca1
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 // indirect
github.com/ipfs/go-todocounter v0.0.2 // indirect
github.com/jackpal/gateway v1.0.6 // indirect

@ -409,6 +409,8 @@ github.com/harmony-one/harmony v1.10.1-0.20210118195726-e5252cf15909 h1:UUAza3XT
github.com/harmony-one/harmony v1.10.1-0.20210118195726-e5252cf15909/go.mod h1:Vie4EPb3ofZ8llU8tWC3LrsntfX5f8kryJ9EqLZGk4g=
github.com/harmony-one/harmony v1.10.2-0.20210122034820-6112100ef5a1 h1:cWg7GEVu1a+XO2PCJUJKC10m+8aeTsL9B84pTjqPovo=
github.com/harmony-one/harmony v1.10.2-0.20210122034820-6112100ef5a1/go.mod h1:QsUfRGisvY6k1KvpzVeBI3VBdHhYLlpVQTEbzrMmw1U=
github.com/harmony-one/harmony v1.10.2-0.20210123081216-6993b9ad0ca1 h1:bu87yqH7Vy5kN33Z9c3/fh5bWkiKBDDnlwY+i2qJook=
github.com/harmony-one/harmony v1.10.2-0.20210123081216-6993b9ad0ca1/go.mod h1:QsUfRGisvY6k1KvpzVeBI3VBdHhYLlpVQTEbzrMmw1U=
github.com/harmony-one/taggedrlp v0.1.2 h1:lAHV4UhBE45W+i7duAAWOgaQNUjDIG6rUydz/5Oqric=
github.com/harmony-one/taggedrlp v0.1.2/go.mod h1:AK7o802368ESS3v4WZI5DzaHv9q0CsdgR9jPVJ6zleg=
github.com/harmony-one/taggedrlp v0.1.4 h1:RZ+qy0VCzT+d/mTfq23gH3an5tSvxOhg6AddLDO6tKw=

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"math/big"
"strconv"
)
// ChainID is a wrapper around the human name for a chain and the actual Big.Int used
@ -52,6 +53,9 @@ func StringToChainID(name string) (*ChainID, error) {
case "dryrun":
return &Chain.MainNet, nil
default:
if chainID, err := strconv.Atoi(name); err == nil && chainID >= 0 {
return &ChainID{Name: fmt.Sprintf("%d", chainID), Value: big.NewInt(int64(chainID))}, nil
}
return nil, fmt.Errorf("unknown chain-id: %s", name)
}
}

@ -16,6 +16,7 @@ import (
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/accounts/keystore"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/numeric"
)
@ -32,7 +33,7 @@ type p []interface{}
type transactionForRPC struct {
params map[string]interface{}
transaction *Transaction
transaction *types.Transaction
// Hex encoded
signature *string
transactionHash *string
@ -104,14 +105,17 @@ func (C *Controller) RawTransaction() string {
return *C.transactionForRPC.signature
}
// TransactionHash - the tx hash
func (C *Controller) TransactionHash() *string {
return C.transactionForRPC.transactionHash
}
// Receipt - the tx receipt
func (C *Controller) Receipt() rpc.Reply {
return C.transactionForRPC.receipt
}
// TransactionErrors - tx errors
func (C *Controller) TransactionErrors() Errors {
return C.transactionErrors
}
@ -304,7 +308,7 @@ func (C *Controller) txConfirmation() {
return
}
time.Sleep(time.Second)
start -= 1
start--
}
}
}

@ -0,0 +1,307 @@
package transaction
import (
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/go-sdk/pkg/address"
"github.com/harmony-one/go-sdk/pkg/common"
"github.com/harmony-one/go-sdk/pkg/rpc"
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/accounts/keystore"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/numeric"
)
type ethTransactionForRPC struct {
params map[string]interface{}
transaction *types.EthTransaction
// Hex encoded
signature *string
transactionHash *string
receipt rpc.Reply
}
// EthController drives the eth transaction signing process
type EthController struct {
executionError error
transactionErrors Errors
messenger rpc.T
sender sender
transactionForRPC ethTransactionForRPC
chain common.ChainID
Behavior behavior
}
// NewEthController initializes a EthController, caller can control behavior via options
func NewEthController(
handler rpc.T, senderKs *keystore.KeyStore,
senderAcct *accounts.Account, chain common.ChainID,
options ...func(*EthController),
) *EthController {
txParams := make(map[string]interface{})
ctrlr := &EthController{
executionError: nil,
messenger: handler,
sender: sender{
ks: senderKs,
account: senderAcct,
},
transactionForRPC: ethTransactionForRPC{
params: txParams,
signature: nil,
transactionHash: nil,
receipt: nil,
},
chain: chain,
Behavior: behavior{false, Software, 0},
}
for _, option := range options {
option(ctrlr)
}
return ctrlr
}
// EthTransactionToJSON dumps JSON representation
func (C *EthController) EthTransactionToJSON(pretty bool) string {
r, _ := C.transactionForRPC.transaction.MarshalJSON()
if pretty {
return common.JSONPrettyFormat(string(r))
}
return string(r)
}
// RawTransaction dumps the signature as string
func (C *EthController) RawTransaction() string {
return *C.transactionForRPC.signature
}
// TransactionHash - the tx hash
func (C *EthController) TransactionHash() *string {
return C.transactionForRPC.transactionHash
}
// Receipt - the tx receipt
func (C *EthController) Receipt() rpc.Reply {
return C.transactionForRPC.receipt
}
// TransactionErrors - tx errors
func (C *EthController) TransactionErrors() Errors {
return C.transactionErrors
}
func (C *EthController) setIntrinsicGas(gasLimit uint64) {
if C.executionError != nil {
return
}
C.transactionForRPC.params["gas-limit"] = gasLimit
}
func (C *EthController) setGasPrice(gasPrice numeric.Dec) {
if C.executionError != nil {
return
}
if gasPrice.Sign() == -1 {
C.executionError = ErrBadTransactionParam
errorMsg := fmt.Sprintf(
"can't set negative gas price: %d", gasPrice,
)
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
return
}
C.transactionForRPC.params["gas-price"] = gasPrice.Mul(nanoAsDec)
}
func (C *EthController) setAmount(amount numeric.Dec) {
if C.executionError != nil {
return
}
if amount.Sign() == -1 {
C.executionError = ErrBadTransactionParam
errorMsg := fmt.Sprintf(
"can't set negative amount: %d", amount,
)
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
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(),
)
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
return
}
C.transactionForRPC.params["transfer-amount"] = amountInAtto
}
func (C *EthController) setReceiver(receiver string) {
C.transactionForRPC.params["receiver"] = address.Parse(receiver)
}
func (C *EthController) setNewTransactionWithDataAndGas(data []byte) {
if C.executionError != nil {
return
}
C.transactionForRPC.transaction = NewEthTransaction(
C.transactionForRPC.params["nonce"].(uint64),
C.transactionForRPC.params["gas-limit"].(uint64),
C.transactionForRPC.params["receiver"].(address.T),
C.transactionForRPC.params["transfer-amount"].(numeric.Dec),
C.transactionForRPC.params["gas-price"].(numeric.Dec),
data,
)
}
func (C *EthController) signAndPrepareTxEncodedForSending() {
if C.executionError != nil {
return
}
signedTransaction, err :=
C.sender.ks.SignEthTx(*C.sender.account, C.transactionForRPC.transaction, C.chain.Value)
if err != nil {
C.executionError = err
return
}
C.transactionForRPC.transaction = signedTransaction
enc, _ := rlp.EncodeToBytes(signedTransaction)
hexSignature := hexutil.Encode(enc)
C.transactionForRPC.signature = &hexSignature
if common.DebugTransaction {
r, _ := signedTransaction.MarshalJSON()
fmt.Println("Signed with ChainID:", C.transactionForRPC.transaction.ChainID())
fmt.Println(common.JSONPrettyFormat(string(r)))
}
}
/*func (C *EthController) hardwareSignAndPrepareTxEncodedForSending() {
if C.executionError != nil {
return
}
enc, signerAddr, err := ledger.SignEthTx(C.transactionForRPC.transaction, C.chain.Value)
if err != nil {
C.executionError = err
return
}
if strings.Compare(signerAddr, address.ToBech32(C.sender.account.Address)) != 0 {
C.executionError = ErrBadTransactionParam
errorMsg := "signature verification failed : sender address doesn't match with ledger hardware addresss"
C.transactionErrors = append(C.transactionErrors, &Error{
ErrMessage: &errorMsg,
TimestampOfRejection: time.Now().Unix(),
})
return
}
hexSignature := hexutil.Encode(enc)
C.transactionForRPC.signature = &hexSignature
}*/
func (C *EthController) sendSignedTx() {
if C.executionError != nil || C.Behavior.DryRun {
return
}
reply, err := C.messenger.SendRPC(rpc.Method.SendRawTransaction, p{C.transactionForRPC.signature})
if err != nil {
C.executionError = err
return
}
r, _ := reply["result"].(string)
C.transactionForRPC.transactionHash = &r
}
func (C *EthController) txConfirmation() {
if C.executionError != nil || C.Behavior.DryRun {
return
}
if C.Behavior.ConfirmationWaitTime > 0 {
txHash := *C.TransactionHash()
start := int(C.Behavior.ConfirmationWaitTime)
for {
r, _ := C.messenger.SendRPC(rpc.Method.GetTransactionReceipt, p{txHash})
if r["result"] != nil {
C.transactionForRPC.receipt = r
return
}
transactionErrors, err := GetError(txHash, C.messenger)
if err != nil {
errMsg := fmt.Sprintf(err.Error())
C.transactionErrors = append(C.transactionErrors, &Error{
TxHashID: &txHash,
ErrMessage: &errMsg,
TimestampOfRejection: time.Now().Unix(),
})
}
C.transactionErrors = append(C.transactionErrors, transactionErrors...)
if len(transactionErrors) > 0 {
C.executionError = fmt.Errorf("error found for transaction hash: %s", txHash)
return
}
if start < 0 {
C.executionError = fmt.Errorf("could not confirm transaction after %d seconds", C.Behavior.ConfirmationWaitTime)
return
}
time.Sleep(time.Second)
start--
}
}
}
// ExecuteEthTransaction is the single entrypoint to execute an eth transaction.
// Each step in transaction creation, execution probably includes a mutation
// Each becomes a no-op if executionError occurred in any previous step
func (C *EthController) ExecuteEthTransaction(
nonce, gasLimit uint64,
to string,
amount, gasPrice numeric.Dec,
inputData []byte,
) error {
// WARNING Order of execution matters
C.setIntrinsicGas(gasLimit)
C.setGasPrice(gasPrice)
C.setAmount(amount)
C.setReceiver(to)
C.transactionForRPC.params["nonce"] = nonce
C.setNewTransactionWithDataAndGas(inputData)
switch C.Behavior.SigningImpl {
case Software:
C.signAndPrepareTxEncodedForSending()
/*case Ledger:
C.hardwareSignAndPrepareTxEncodedForSending()*/
}
C.sendSignedTx()
C.txConfirmation()
return C.executionError
}
// TODO: add logic to create staking transactions in the SDK.

@ -9,17 +9,25 @@ import (
"github.com/harmony-one/harmony/numeric"
)
type Transaction = types.Transaction
// NewTransaction - create a new Transaction based on supplied params
func NewTransaction(
nonce, gasLimit uint64,
to address.T,
shardID, toShardID uint32,
amount, gasPrice numeric.Dec,
data []byte) *Transaction {
data []byte) *types.Transaction {
return types.NewCrossShardTransaction(nonce, &to, shardID, toShardID, amount.TruncateInt(), gasLimit, gasPrice.TruncateInt(), data[:])
}
// NewEthTransaction - create a new Transaction based on supplied params
func NewEthTransaction(
nonce, gasLimit uint64,
to address.T,
amount, gasPrice numeric.Dec,
data []byte) *types.EthTransaction {
return types.NewEthTransaction(nonce, to, amount.TruncateInt(), gasLimit, gasPrice.TruncateInt(), data[:])
}
// GetNextNonce returns the nonce on-chain (finalized transactions)
func GetNextNonce(addr string, messenger rpc.T) uint64 {
transactionCountRPCReply, err :=
@ -48,6 +56,7 @@ func GetNextPendingNonce(addr string, messenger rpc.T) uint64 {
return n.Uint64()
}
func IsValid(tx *Transaction) bool {
// IsValid - whether or not a tx is valid
func IsValid(tx *types.Transaction) bool {
return true
}

Loading…
Cancel
Save