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 }