Skip to content

Commit 4d015bb

Browse files
authored
Merge pull request #541
Update signing issue for eth_sendTransactionAsync RPC call
2 parents 581eed5 + 7a282ff commit 4d015bb

File tree

3 files changed

+168
-63
lines changed

3 files changed

+168
-63
lines changed

docs/api.md

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Returns the storage root of given address (Contract/Account etc)
6161

6262
##### Parameters
6363

64-
address, block number (hex)
64+
1. `address`: `String` - The address to fetch the storage root for in hex
65+
2. `block`: `String` - (optional) The block number to look at in hex (e.g. `0x15` for block 21). Uses the latest block if not specified.
6566

6667
##### Returns
6768

@@ -99,7 +100,7 @@ curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_st
99100
// After private state of the contract is changed from '42' to '99'
100101
// Request
101102

102-
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17","0x2"], "id": 67}'
103+
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17", "0x2"], "id": 67}'
103104

104105
// Response
105106
{
@@ -117,7 +118,7 @@ Returns the unencrypted payload from Tessera/constellation
117118

118119
##### Parameters
119120

120-
Transaction payload hash in Hex format
121+
1. `id`: `String` - the HEX formatted generated Sha3-512 hash of the encrypted payload from the Private Transaction Manager. This is seen in the transaction as the `input` field
121122

122123
##### Returns
123124

@@ -145,3 +146,110 @@ curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_getQ
145146
"result": "0x"
146147
}
147148
```
149+
150+
***
151+
152+
#### eth_sendTransactionAsync
153+
154+
Sends a transaction to the network asynchronously. This will return
155+
immediately, potentially before the transaction has been submitted to the
156+
transaction pool. A callback can be provided to receive the result of
157+
submitting the transaction; a server must be set up to receive POST requests
158+
at the given URL.
159+
160+
##### Parameters
161+
162+
1. `Object` - The transaction object to send:
163+
- `from`: `String` - The address for the sending account. Uses the `web3.eth.defaultAccount` property, if not specified.
164+
- `to`: `String` - (optional) The destination address of the message, left undefined for a contract-creation transaction.
165+
- `value`: `Number|String|BigNumber` - (optional) The value transferred for the transaction in Wei, also the endowment if it's a contract-creation transaction.
166+
- `gas`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded).
167+
- <strike>`gasPrice`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The price of gas for this transaction in wei, defaults to the mean network gas price.</strike>
168+
- `data`: `String` - (optional) Either a [byte string](https://github.com/ethereum/wiki/wiki/Solidity,-Docs-and-ABI) containing the associated data of the message, or in the case of a contract-creation transaction, the initialisation code.
169+
- `nonce`: `Number` - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.
170+
- `privateFrom`: `String` - (optional) When sending a private transaction, the sending party's base64-encoded public key to use. If not present *and* passing `privateFor`, use the default key as configured in the `TransactionManager`.
171+
- `privateFor`: `List<String>` - (optional) When sending a private transaction, an array of the recipients' base64-encoded public keys.
172+
- `callbackUrl`: `String` - (optional) the URL to perform a POST request to to post the result of submitted the transaction
173+
174+
##### Returns
175+
176+
1. `String` - The empty hash, defined as `0x0000000000000000000000000000000000000000000000000000000000000000`
177+
178+
The callback URL receives the following object:
179+
180+
2. `Object` - The result object:
181+
- `id`: `String` - the identifier in the original RPC call, used to match this result to the request
182+
- `txHash`: `String` - the transaction hash that was generated, if successful
183+
- `error`: `String` - the error that occurred whilst submitting the transaction.
184+
185+
If the transaction was a contract creation use `web3.eth.getTransactionReceipt()` to get the contract address, after the transaction was mined.
186+
187+
##### Example
188+
189+
For the RPC call and the immediate response:
190+
191+
```
192+
// Request
193+
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_sendTransactionAsync", "params":[{"from":"0xed9d02e382b34818e88b88a309c7fe71e65f419d", "data": "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a", "gas": "0x47b760", "privateFor": ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}], "id":67}'
194+
195+
// Response
196+
{
197+
"id": 67,
198+
"jsonrpc": "2.0",
199+
"result": "0x0000000000000000000000000000000000000000000000000000000000000000"
200+
}
201+
202+
203+
// Request
204+
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_sendTransactionAsync", "params":[{"from":"0xe2e382b3b8871e65f419d", "data": "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a", "gas": "0x47b760", "privateFor": ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}], "id":67}'
205+
206+
//If a syntactic error occured with the RPC call.
207+
//In this example the wallet address is the wrong length
208+
//so the error is it cannot convert the parameter to the correct type
209+
//it is NOT an error relating the the address not being managed by this node.
210+
211+
//Response
212+
{
213+
"id": 67,
214+
"jsonrpc": "2.0",
215+
"error": {
216+
"code": -32602,
217+
"message": "invalid argument 0: json: cannot unmarshal hex string of odd length into Go struct field AsyncSendTxArgs.from of type common.Address"
218+
}
219+
}
220+
```
221+
222+
If the callback URL is provided, the following response will be received after
223+
the transaction has been submitted; this example assumes a webserver that can
224+
be accessed by calling http://localhost:8080 has been set up to accept POST
225+
requests:
226+
227+
```
228+
229+
230+
// Request
231+
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_sendTransactionAsync", "params":[{"from":"0xed9d02e382b34818e88b88a309c7fe71e65f419d", "data": "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a", "gas": "0x47b760", "privateFor": ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="], "callbackUrl": "http://localhost:8080"}], "id":67}'
232+
233+
// Response
234+
//Note that the ID is the same in the callback as the request - this can be used to match the request to the response.
235+
{
236+
"id": 67,
237+
"txHash": "0x75ebbf4fbe29355fc8a4b8d1e14ecddf0228b64ef41e6d2fce56047650e2bf17"
238+
}
239+
240+
241+
242+
243+
// Request
244+
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_sendTransactionAsync", "params":[{"from":"0xae9bc6cd5145e67fbd1887a5145271fd182f0ee7", "callbackUrl": "http://localhost:8080", "data": "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a", "gas": "0x47b760", "privateFor": ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}], "id":67}'
245+
246+
//If a semantic error occured with the RPC call.
247+
//In this example the wallet address is not managed by the node
248+
//So the RPC call will succeed (giving the empty hash), but the callback will show a failure
249+
250+
// In the callback
251+
{
252+
"id": 67,
253+
"error":"unknown account"
254+
}
255+
```

internal/ethapi/api.go

Lines changed: 52 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,8 +1071,11 @@ type SendTxArgs struct {
10711071
Data hexutil.Bytes `json:"data"`
10721072
Nonce *hexutil.Uint64 `json:"nonce"`
10731073

1074+
//Quorum
10741075
PrivateFrom string `json:"privateFrom"`
10751076
PrivateFor []string `json:"privateFor"`
1077+
PrivateTxType string `json:"restriction"`
1078+
//End-Quorum
10761079
}
10771080

10781081
// prepareSendTxArgs is a helper function that fills in default values for unspecified tx fields.
@@ -1097,6 +1100,11 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
10971100
}
10981101
args.Nonce = (*hexutil.Uint64)(&nonce)
10991102
}
1103+
//Quorum
1104+
if args.PrivateTxType == "" {
1105+
args.PrivateTxType = "restricted"
1106+
}
1107+
//End-Quorum
11001108
return nil
11011109
}
11021110

@@ -1444,6 +1452,7 @@ func (s *PublicNetAPI) Version() string {
14441452
return fmt.Sprintf("%d", s.networkVersion)
14451453
}
14461454

1455+
// Quorum
14471456
// Please note: This is a temporary integration to improve performance in high-latency
14481457
// environments when sending many private transactions. It will be removed at a later
14491458
// date when account management is handled outside Ethereum.
@@ -1453,8 +1462,13 @@ type AsyncSendTxArgs struct {
14531462
CallbackUrl string `json:"callbackUrl"`
14541463
}
14551464

1456-
type AsyncResult struct {
1465+
type AsyncResultSuccess struct {
1466+
Id string `json:"id,omitempty"`
14571467
TxHash common.Hash `json:"txHash"`
1468+
}
1469+
1470+
type AsyncResultFailure struct {
1471+
Id string `json:"id,omitempty"`
14581472
Error string `json:"error"`
14591473
}
14601474

@@ -1463,65 +1477,37 @@ type Async struct {
14631477
sem chan struct{}
14641478
}
14651479

1466-
func (a *Async) send(ctx context.Context, s *PublicTransactionPoolAPI, asyncArgs AsyncSendTxArgs) {
1467-
res := new(AsyncResult)
1480+
func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSendTxArgs) {
1481+
1482+
txHash, err := s.SendTransaction(ctx, asyncArgs.SendTxArgs)
1483+
14681484
if asyncArgs.CallbackUrl != "" {
1469-
defer func() {
1470-
buf := new(bytes.Buffer)
1471-
err := json.NewEncoder(buf).Encode(res)
1472-
if err != nil {
1473-
log.Info("Error encoding callback JSON: %v", err)
1474-
return
1475-
}
1476-
_, err = http.Post(asyncArgs.CallbackUrl, "application/json", buf)
1477-
if err != nil {
1478-
log.Info("Error sending callback: %v", err)
1479-
return
1480-
}
1481-
}()
1482-
}
1483-
args := asyncArgs.SendTxArgs
1484-
err := args.setDefaults(ctx, s.b)
1485-
if err != nil {
1486-
log.Info("Async.send: Error doing setDefaults: %v", err)
1487-
res.Error = err.Error()
1488-
return
1489-
}
1490-
b, err := private.P.Send([]byte(args.Data), args.PrivateFrom, args.PrivateFor)
1491-
if err != nil {
1492-
log.Info("Error running Private.P.Send", "err", err)
1493-
res.Error = err.Error()
1494-
return
1495-
}
1496-
res.TxHash, err = a.save(ctx, s, args, b)
1497-
if err != nil {
1498-
res.Error = err.Error()
1499-
}
1500-
}
15011485

1502-
func (a *Async) save(ctx context.Context, s *PublicTransactionPoolAPI, args SendTxArgs, data []byte) (common.Hash, error) {
1503-
a.Lock()
1504-
defer a.Unlock()
1505-
if args.Nonce == nil {
1506-
nonce, err := s.b.GetPoolNonce(ctx, args.From)
1486+
//don't need to nil check this since id is required for every geth rpc call
1487+
//even though this is stated in the specification as an "optional" parameter
1488+
jsonId := ctx.Value("id").(*json.RawMessage)
1489+
id := string(*jsonId)
1490+
1491+
var resultResponse interface{}
15071492
if err != nil {
1508-
return common.Hash{}, err
1493+
resultResponse = &AsyncResultFailure{Id: id, Error: err.Error()}
1494+
} else {
1495+
resultResponse = &AsyncResultSuccess{Id: id, TxHash: txHash}
15091496
}
1510-
args.Nonce = (*hexutil.Uint64)(&nonce)
1511-
}
1512-
var tx *types.Transaction
1513-
if args.To == nil {
1514-
tx = types.NewContractCreation((uint64)(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), data)
1515-
} else {
1516-
tx = types.NewTransaction((uint64)(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), data)
1517-
}
15181497

1519-
signed, err := s.sign(args.From, tx)
1520-
if err != nil {
1521-
return common.Hash{}, err
1498+
buf := new(bytes.Buffer)
1499+
err := json.NewEncoder(buf).Encode(resultResponse)
1500+
if err != nil {
1501+
log.Info("Error encoding callback JSON: %v", err)
1502+
return
1503+
}
1504+
_, err = http.Post(asyncArgs.CallbackUrl, "application/json", buf)
1505+
if err != nil {
1506+
log.Info("Error sending callback: %v", err)
1507+
return
1508+
}
15221509
}
15231510

1524-
return submitTransaction(ctx, s.b, signed, args.PrivateFor != nil)
15251511
}
15261512

15271513
func newAsync(n int) *Async {
@@ -1544,12 +1530,18 @@ var async = newAsync(100)
15441530
// Please note: This is a temporary integration to improve performance in high-latency
15451531
// environments when sending many private transactions. It will be removed at a later
15461532
// date when account management is handled outside Ethereum.
1547-
func (s *PublicTransactionPoolAPI) SendTransactionAsync(ctx context.Context, args AsyncSendTxArgs) {
1548-
async.sem <- struct{}{}
1549-
go func() {
1550-
async.send(ctx, s, args)
1551-
<-async.sem
1552-
}()
1533+
func (s *PublicTransactionPoolAPI) SendTransactionAsync(ctx context.Context, args AsyncSendTxArgs) (common.Hash, error){
1534+
1535+
select {
1536+
case async.sem <- struct{}{}:
1537+
go func() {
1538+
s.send(ctx, args)
1539+
<-async.sem
1540+
}()
1541+
return common.Hash{}, nil
1542+
default:
1543+
return common.Hash{}, errors.New("too many concurrent requests")
1544+
}
15531545
}
15541546

15551547
// GetQuorumPayload returns the contents of a private transaction
@@ -1576,3 +1568,4 @@ func (s *PublicBlockChainAPI) GetQuorumPayload(digestHex string) (string, error)
15761568
}
15771569
return fmt.Sprintf("0x%x", data), nil
15781570
}
1571+
//End-Quorum

rpc/server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,13 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque
299299
return codec.CreateErrorResponse(&req.id, rpcErr), nil
300300
}
301301

302+
//Quorum
303+
//Pass the request ID to the method as part of the context, in case the method needs it later
304+
contextWithId := context.WithValue(ctx, "id", req.id)
305+
//End-Quorum
302306
arguments := []reflect.Value{req.callb.rcvr}
303307
if req.callb.hasCtx {
304-
arguments = append(arguments, reflect.ValueOf(ctx))
308+
arguments = append(arguments, reflect.ValueOf(contextWithId))
305309
}
306310
if len(req.args) > 0 {
307311
arguments = append(arguments, req.args...)

0 commit comments

Comments
 (0)