github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/rpc/web3/ethclient/transact_client.go (about)

     1  package ethclient
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/hyperledger/burrow/acm"
     8  	"github.com/hyperledger/burrow/crypto"
     9  	"github.com/hyperledger/burrow/encoding/web3hex"
    10  	"github.com/hyperledger/burrow/execution/exec"
    11  	"github.com/hyperledger/burrow/txs"
    12  	"github.com/hyperledger/burrow/txs/payload"
    13  	"google.golang.org/grpc"
    14  )
    15  
    16  const BasicGasLimit = 21000
    17  
    18  // Provides a partial implementation of the GRPC-generated TransactClient suitable for testing Vent on Ethereum
    19  type TransactClient struct {
    20  	client   ethClient
    21  	chainID  string
    22  	accounts []acm.AddressableSigner
    23  }
    24  
    25  type ethClient interface {
    26  	AwaitTransaction(ctx context.Context, txHash string) (*Receipt, error)
    27  	SendTransaction(tx *EthSendTransactionParam) (string, error)
    28  	SendRawTransaction(txHex string) (string, error)
    29  	GetTransactionCount(address crypto.Address) (string, error)
    30  	NetVersion() (string, error)
    31  	GasPrice() (string, error)
    32  }
    33  
    34  func NewTransactClient(client ethClient) *TransactClient {
    35  	return &TransactClient{
    36  		client: client,
    37  	}
    38  }
    39  
    40  func (cli *TransactClient) WithAccounts(signers ...acm.AddressableSigner) *TransactClient {
    41  	return &TransactClient{
    42  		client:   cli.client,
    43  		accounts: append(cli.accounts, signers...),
    44  	}
    45  }
    46  
    47  func (cli *TransactClient) CallTxSync(ctx context.Context, tx *payload.CallTx,
    48  	opts ...grpc.CallOption) (*exec.TxExecution, error) {
    49  
    50  	var signer acm.AddressableSigner
    51  
    52  	for _, sa := range cli.accounts {
    53  		if sa.GetAddress() == tx.Input.Address {
    54  			signer = sa
    55  			break
    56  		}
    57  	}
    58  
    59  	// Only set nonce for tx we sign, otherwise let server do it
    60  	err := cli.completeTx(tx, signer != nil)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("could not set values on transaction")
    63  	}
    64  
    65  	var txHash string
    66  	if signer == nil {
    67  		txHash, err = cli.SendTransaction(tx)
    68  	} else {
    69  		txHash, err = cli.SendRawTransaction(tx, signer)
    70  	}
    71  	if err != nil {
    72  		return nil, fmt.Errorf("could not send ethereum transaction: %w", err)
    73  	}
    74  
    75  	fmt.Printf("Waiting for tranasaction %s to be confirmed...\n", txHash)
    76  	receipt, err := cli.client.AwaitTransaction(ctx, txHash)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	d := new(web3hex.Decoder)
    82  
    83  	header := &exec.TxHeader{
    84  		TxType: payload.TypeCall,
    85  		TxHash: d.Bytes(receipt.TransactionHash),
    86  		Height: d.Uint64(receipt.BlockNumber),
    87  		Index:  d.Uint64(receipt.TransactionIndex),
    88  	}
    89  
    90  	// Attempt to provide sufficient return values to satisfy Vent's needs.
    91  	return &exec.TxExecution{
    92  		TxHeader: header,
    93  		Receipt: &txs.Receipt{
    94  			TxType:          header.TxType,
    95  			TxHash:          header.TxHash,
    96  			CreatesContract: receipt.ContractAddress != "",
    97  			ContractAddress: d.Address(receipt.ContractAddress),
    98  		},
    99  	}, d.Err()
   100  }
   101  
   102  func (cli *TransactClient) SendTransaction(tx *payload.CallTx) (string, error) {
   103  	var to string
   104  	if tx.Address != nil {
   105  		to = web3hex.Encoder.Address(*tx.Address)
   106  	}
   107  
   108  	var nonce string
   109  	if tx.Input.Sequence != 0 {
   110  		nonce = web3hex.Encoder.Uint64OmitEmpty(tx.Input.Sequence)
   111  	}
   112  
   113  	param := &EthSendTransactionParam{
   114  		From:     web3hex.Encoder.Address(tx.Input.Address),
   115  		To:       to,
   116  		Gas:      web3hex.Encoder.Uint64OmitEmpty(tx.GasLimit),
   117  		GasPrice: web3hex.Encoder.Uint64OmitEmpty(tx.GasPrice),
   118  		Value:    web3hex.Encoder.Uint64OmitEmpty(tx.Input.Amount),
   119  		Data:     web3hex.Encoder.BytesTrim(tx.Data),
   120  		Nonce:    nonce,
   121  	}
   122  
   123  	return cli.client.SendTransaction(param)
   124  }
   125  
   126  func (cli *TransactClient) SendRawTransaction(tx *payload.CallTx, signer acm.AddressableSigner) (string, error) {
   127  	chainID, err := cli.GetChainID()
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  	txEnv := txs.Enclose(chainID, tx)
   132  
   133  	txEnv.Encoding = txs.Envelope_RLP
   134  
   135  	err = txEnv.Sign(signer)
   136  	if err != nil {
   137  		return "", fmt.Errorf("could not sign Ethereum transaction: %w", err)
   138  	}
   139  
   140  	rawTx, err := txs.EthRawTxFromEnvelope(txEnv)
   141  	if err != nil {
   142  		return "", fmt.Errorf("could not generate Ethereum raw transaction: %w", err)
   143  	}
   144  
   145  	bs, err := rawTx.Marshal()
   146  	if err != nil {
   147  		return "", fmt.Errorf("could not marshal Ethereum raw transaction: %w", err)
   148  	}
   149  
   150  	return cli.client.SendRawTransaction(web3hex.Encoder.BytesTrim(bs))
   151  }
   152  
   153  func (cli *TransactClient) GetChainID() (string, error) {
   154  	if cli.chainID == "" {
   155  		var err error
   156  		cli.chainID, err = cli.client.NetVersion()
   157  		if err != nil {
   158  			return "", fmt.Errorf("TransactClient could not get ChainID: %w", err)
   159  		}
   160  	}
   161  	return cli.chainID, nil
   162  }
   163  
   164  func (cli *TransactClient) GetGasPrice() (uint64, error) {
   165  	gasPrice, err := cli.client.GasPrice()
   166  	if err != nil {
   167  		return 0, fmt.Errorf("could not get gas price: %w", err)
   168  	}
   169  	d := new(web3hex.Decoder)
   170  	return d.Uint64(gasPrice), d.Err()
   171  }
   172  
   173  func (cli *TransactClient) GetTransactionCount(address crypto.Address) (uint64, error) {
   174  	count, err := cli.client.GetTransactionCount(address)
   175  	if err != nil {
   176  		return 0, fmt.Errorf("could not get transaction acount for address %s: %w", address, err)
   177  	}
   178  	d := new(web3hex.Decoder)
   179  	return d.Uint64(count), d.Err()
   180  }
   181  
   182  func (cli *TransactClient) completeTx(tx *payload.CallTx, setNonce bool) error {
   183  	if tx.GasLimit == 0 {
   184  		tx.GasLimit = BasicGasLimit
   185  	}
   186  	var err error
   187  	if tx.GasPrice == 0 {
   188  		tx.GasPrice, err = cli.GetGasPrice()
   189  		if err != nil {
   190  			return err
   191  		}
   192  	}
   193  	if setNonce && tx.Input.Sequence == 0 {
   194  		tx.Input.Sequence, err = cli.GetTransactionCount(tx.Input.Address)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  	return nil
   200  }