diff --git a/docs/api.md b/docs/api.md index 2c2a6c0cfa..d7e2b84d0e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -61,7 +61,8 @@ Returns the storage root of given address (Contract/Account etc) ##### Parameters -address, block number (hex) +1. `address`: `String` - The address to fetch the storage root for in hex +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. ##### Returns @@ -99,7 +100,7 @@ curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_st // After private state of the contract is changed from '42' to '99' // Request -curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17","0x2"], "id": 67}' +curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17", "0x2"], "id": 67}' // Response { @@ -117,7 +118,7 @@ Returns the unencrypted payload from Tessera/constellation ##### Parameters -Transaction payload hash in Hex format +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 ##### Returns @@ -145,3 +146,110 @@ curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_getQ "result": "0x" } ``` + +*** + +#### eth_sendTransactionAsync + + Sends a transaction to the network asynchronously. This will return + immediately, potentially before the transaction has been submitted to the + transaction pool. A callback can be provided to receive the result of + submitting the transaction; a server must be set up to receive POST requests + at the given URL. + + ##### Parameters + + 1. `Object` - The transaction object to send: + - `from`: `String` - The address for the sending account. Uses the `web3.eth.defaultAccount` property, if not specified. + - `to`: `String` - (optional) The destination address of the message, left undefined for a contract-creation transaction. + - `value`: `Number|String|BigNumber` - (optional) The value transferred for the transaction in Wei, also the endowment if it's a contract-creation transaction. + - `gas`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded). + - `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. + - `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. + - `nonce`: `Number` - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. + - `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`. + - `privateFor`: `List` - (optional) When sending a private transaction, an array of the recipients' base64-encoded public keys. + - `callbackUrl`: `String` - (optional) the URL to perform a POST request to to post the result of submitted the transaction + + ##### Returns + + 1. `String` - The empty hash, defined as `0x0000000000000000000000000000000000000000000000000000000000000000` + + The callback URL receives the following object: + + 2. `Object` - The result object: + - `id`: `String` - the identifier in the original RPC call, used to match this result to the request + - `txHash`: `String` - the transaction hash that was generated, if successful + - `error`: `String` - the error that occurred whilst submitting the transaction. + + If the transaction was a contract creation use `web3.eth.getTransactionReceipt()` to get the contract address, after the transaction was mined. + +##### Example + +For the RPC call and the immediate response: + +``` +// Request +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}' + +// Response +{ + "id": 67, + "jsonrpc": "2.0", + "result": "0x0000000000000000000000000000000000000000000000000000000000000000" +} + + +// Request +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}' + +//If a syntactic error occured with the RPC call. +//In this example the wallet address is the wrong length +//so the error is it cannot convert the parameter to the correct type +//it is NOT an error relating the the address not being managed by this node. + +//Response +{ + "id": 67, + "jsonrpc": "2.0", + "error": { + "code": -32602, + "message": "invalid argument 0: json: cannot unmarshal hex string of odd length into Go struct field AsyncSendTxArgs.from of type common.Address" + } +} +``` + +If the callback URL is provided, the following response will be received after +the transaction has been submitted; this example assumes a webserver that can +be accessed by calling http://localhost:8080 has been set up to accept POST +requests: + +``` + + +// Request +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}' + +// Response +//Note that the ID is the same in the callback as the request - this can be used to match the request to the response. +{ + "id": 67, + "txHash": "0x75ebbf4fbe29355fc8a4b8d1e14ecddf0228b64ef41e6d2fce56047650e2bf17" +} + + + + +// Request +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}' + +//If a semantic error occured with the RPC call. +//In this example the wallet address is not managed by the node +//So the RPC call will succeed (giving the empty hash), but the callback will show a failure + +// In the callback +{ + "id": 67, + "error":"unknown account" +} +``` diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 65a644e221..df7e5642be 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1069,8 +1069,11 @@ type SendTxArgs struct { Data hexutil.Bytes `json:"data"` Nonce *hexutil.Uint64 `json:"nonce"` + //Quorum PrivateFrom string `json:"privateFrom"` PrivateFor []string `json:"privateFor"` + PrivateTxType string `json:"restriction"` + //End-Quorum } // prepareSendTxArgs is a helper function that fills in default values for unspecified tx fields. @@ -1095,6 +1098,11 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { } args.Nonce = (*hexutil.Uint64)(&nonce) } + //Quorum + if args.PrivateTxType == "" { + args.PrivateTxType = "restricted" + } + //End-Quorum return nil } @@ -1437,6 +1445,7 @@ func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } +// Quorum // Please note: This is a temporary integration to improve performance in high-latency // environments when sending many private transactions. It will be removed at a later // date when account management is handled outside Ethereum. @@ -1446,8 +1455,13 @@ type AsyncSendTxArgs struct { CallbackUrl string `json:"callbackUrl"` } -type AsyncResult struct { +type AsyncResultSuccess struct { + Id string `json:"id,omitempty"` TxHash common.Hash `json:"txHash"` +} + +type AsyncResultFailure struct { + Id string `json:"id,omitempty"` Error string `json:"error"` } @@ -1456,65 +1470,37 @@ type Async struct { sem chan struct{} } -func (a *Async) send(ctx context.Context, s *PublicTransactionPoolAPI, asyncArgs AsyncSendTxArgs) { - res := new(AsyncResult) +func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSendTxArgs) { + + txHash, err := s.SendTransaction(ctx, asyncArgs.SendTxArgs) + if asyncArgs.CallbackUrl != "" { - defer func() { - buf := new(bytes.Buffer) - err := json.NewEncoder(buf).Encode(res) - if err != nil { - log.Info("Error encoding callback JSON: %v", err) - return - } - _, err = http.Post(asyncArgs.CallbackUrl, "application/json", buf) - if err != nil { - log.Info("Error sending callback: %v", err) - return - } - }() - } - args := asyncArgs.SendTxArgs - err := args.setDefaults(ctx, s.b) - if err != nil { - log.Info("Async.send: Error doing setDefaults: %v", err) - res.Error = err.Error() - return - } - b, err := private.P.Send([]byte(args.Data), args.PrivateFrom, args.PrivateFor) - if err != nil { - log.Info("Error running Private.P.Send", "err", err) - res.Error = err.Error() - return - } - res.TxHash, err = a.save(ctx, s, args, b) - if err != nil { - res.Error = err.Error() - } -} -func (a *Async) save(ctx context.Context, s *PublicTransactionPoolAPI, args SendTxArgs, data []byte) (common.Hash, error) { - a.Lock() - defer a.Unlock() - if args.Nonce == nil { - nonce, err := s.b.GetPoolNonce(ctx, args.From) + //don't need to nil check this since id is required for every geth rpc call + //even though this is stated in the specification as an "optional" parameter + jsonId := ctx.Value("id").(*json.RawMessage) + id := string(*jsonId) + + var resultResponse interface{} if err != nil { - return common.Hash{}, err + resultResponse = &AsyncResultFailure{Id: id, Error: err.Error()} + } else { + resultResponse = &AsyncResultSuccess{Id: id, TxHash: txHash} } - args.Nonce = (*hexutil.Uint64)(&nonce) - } - var tx *types.Transaction - if args.To == nil { - tx = types.NewContractCreation((uint64)(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), data) - } else { - tx = types.NewTransaction((uint64)(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), data) - } - signed, err := s.sign(args.From, tx) - if err != nil { - return common.Hash{}, err + buf := new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(resultResponse) + if err != nil { + log.Info("Error encoding callback JSON: %v", err) + return + } + _, err = http.Post(asyncArgs.CallbackUrl, "application/json", buf) + if err != nil { + log.Info("Error sending callback: %v", err) + return + } } - return submitTransaction(ctx, s.b, signed, args.PrivateFor != nil) } func newAsync(n int) *Async { @@ -1537,12 +1523,18 @@ var async = newAsync(100) // Please note: This is a temporary integration to improve performance in high-latency // environments when sending many private transactions. It will be removed at a later // date when account management is handled outside Ethereum. -func (s *PublicTransactionPoolAPI) SendTransactionAsync(ctx context.Context, args AsyncSendTxArgs) { - async.sem <- struct{}{} - go func() { - async.send(ctx, s, args) - <-async.sem - }() +func (s *PublicTransactionPoolAPI) SendTransactionAsync(ctx context.Context, args AsyncSendTxArgs) (common.Hash, error){ + + select { + case async.sem <- struct{}{}: + go func() { + s.send(ctx, args) + <-async.sem + }() + return common.Hash{}, nil + default: + return common.Hash{}, errors.New("too many concurrent requests") + } } // GetQuorumPayload returns the contents of a private transaction @@ -1569,3 +1561,4 @@ func (s *PublicBlockChainAPI) GetQuorumPayload(digestHex string) (string, error) } return fmt.Sprintf("0x%x", data), nil } +//End-Quorum \ No newline at end of file diff --git a/rpc/server.go b/rpc/server.go index 30c288349e..5111c0e845 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -299,9 +299,13 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque return codec.CreateErrorResponse(&req.id, rpcErr), nil } + //Quorum + //Pass the request ID to the method as part of the context, in case the method needs it later + contextWithId := context.WithValue(ctx, "id", req.id) + //End-Quorum arguments := []reflect.Value{req.callb.rcvr} if req.callb.hasCtx { - arguments = append(arguments, reflect.ValueOf(ctx)) + arguments = append(arguments, reflect.ValueOf(contextWithId)) } if len(req.args) > 0 { arguments = append(arguments, req.args...)