Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/support private sendRawTransaction #591

Merged
merged 3 commits into from Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/api.md
Expand Up @@ -3,6 +3,8 @@

## Privacy APIs

#### eth.sendTransaction

__To support private transactions in Quorum, the `web3.eth.sendTransaction(object)` API method has been modified.__

```js
Expand Down Expand Up @@ -50,6 +52,55 @@ web3.eth.sendTransaction({
```
***

#### eth.sendRawPrivateTransaction

__To support sending raw transactions in Quorum, the `web3.eth.sendRawPrivateTransaction(string, object)` API method has been created.__

```js
web3.eth.sendRawPrivateTransaction(signedTransactionData [, privateData] [, callback])
```

Sends a pre-signed transaction. For example can be signed using: https://github.com/SilentCicero/ethereumjs-accounts

__Important:__ Please note that before calling this API, a `storeraw` api need to be called first to Quorum's private transaction manager. Instructions on how to do this can be found [here](https://github.com/jpmorganchase/tessera/wiki/Interface-&-API).

##### Parameters
1. `String` - Signed transaction data in HEX format
2. `Object` - Private data to send
- `privateFor`: `List<String>` - When sending a private transaction, an array of the recipients' base64-encoded public keys.
3. `Function` - (optional) If you pass a callback the HTTP request is made asynchronous. See [this note](#using-callbacks) for details.

##### Returns
`String` - The 32 Bytes transaction hash as HEX string.
If the transaction was a contract creation use [web3.eth.getTransactionReceipt()](#web3ethgettransactionreceipt) to get the contract address, after the transaction was mined.


##### Example
```js
var Tx = require('ethereumjs-tx');
var privateKey = new Buffer('e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex')
var rawTx = {
nonce: '0x00',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: '0x0000000000000000000000000000000000000000',
value: '0x00',
// This data should be the hex value of the hash returned by Quorum's privacy transaction manager after invoking storeraw api
data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057'
}
var tx = new Tx(rawTx);
tx.sign(privateKey);
var serializedTx = tx.serialize();
//console.log(serializedTx.toString('hex'));
//f889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f
web3.eth.sendRawPrivateTransaction('0x' + serializedTx.toString('hex'), {privateFor: ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}, function(err, hash) {
if (!err)
console.log(hash); // "0x7f9fade1c0d57a7af66ab4ead79fade1c0d57a7af66ab4ead7c2c2eb7b11a91385"
});
```



## JSON RPC Privacy API Reference

__In addition to the JSON-RPC provided by Ethereum, Quorum exposes below two API calls.__
Expand Down
41 changes: 38 additions & 3 deletions internal/ethapi/api.go
Expand Up @@ -1168,12 +1168,17 @@ type SendTxArgs struct {
Input *hexutil.Bytes `json:"input"`

//Quorum
PrivateFrom string `json:"privateFrom"`
PrivateFor []string `json:"privateFor"`
PrivateTxType string `json:"restriction"`
PrivateFrom string `json:"privateFrom"`
PrivateFor []string `json:"privateFor"`
PrivateTxType string `json:"restriction"`
//End-Quorum
}

// SendRawTxArgs represents the arguments to submit a new signed private transaction into the transaction pool.
type SendRawTxArgs struct {
PrivateFor []string `json:"privateFor"`
}

// setDefaults is a helper function that fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
if args.Gas == nil {
Expand Down Expand Up @@ -1315,6 +1320,35 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
return submitTransaction(ctx, s.b, tx, tx.IsPrivate())
}

// SendRawPrivateTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicTransactionPoolAPI) SendRawPrivateTransaction(ctx context.Context, encodedTx hexutil.Bytes, args SendRawTxArgs) (common.Hash, error) {

tx := new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}

txHash := []byte(tx.Data())
isPrivate := args.PrivateFor != nil

if isPrivate {
if len(txHash) > 0 {
//Send private transaction to privacy manager
log.Info("sending private tx", "data", fmt.Sprintf("%x", txHash), "privatefor", args.PrivateFor)
result, err := private.P.SendSignedTx(txHash, args.PrivateFor)
log.Info("sent private tx", "result", fmt.Sprintf("%x", result), "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
}
} else {
return common.Hash{}, fmt.Errorf("transaction is not private")
}

return submitTransaction(ctx, s.b, tx, isPrivate)
}

// Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
Expand Down Expand Up @@ -1688,4 +1722,5 @@ func (s *PublicBlockChainAPI) GetQuorumPayload(digestHex string) (string, error)
}
return fmt.Sprintf("0x%x", data), nil
}

//End-Quorum
6 changes: 6 additions & 0 deletions internal/web3ext/web3ext.go
Expand Up @@ -396,6 +396,12 @@ const Eth_JS = `
web3._extend({
property: 'eth',
methods: [
new web3._extend.Method({
name: 'sendRawPrivateTransaction',
call: 'eth_sendRawPrivateTransaction',
params: 2,
inputFormatter: [null, null]
}),
new web3._extend.Method({
name: 'sign',
call: 'eth_sign',
Expand Down
12 changes: 12 additions & 0 deletions private/constellation/constellation.go
Expand Up @@ -33,6 +33,18 @@ func (g *Constellation) Send(data []byte, from string, to []string) (out []byte,
return out, nil
}

func (g *Constellation) SendSignedTx(data []byte, to []string) (out []byte, err error) {
if g.isConstellationNotInUse {
return nil, ErrConstellationIsntInit
}
out, err = g.node.SendSignedPayload(data, to)
if err != nil {
return nil, err
}
return out, nil
}


func (g *Constellation) Receive(data []byte) ([]byte, error) {
if g.isConstellationNotInUse {
return nil, nil
Expand Down
24 changes: 24 additions & 0 deletions private/constellation/node.go
Expand Up @@ -106,6 +106,30 @@ func (c *Client) SendPayload(pl []byte, b64From string, b64To []string) ([]byte,
return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body))
}

func (c *Client) SendSignedPayload(signedPayload []byte, b64To []string) ([]byte, error) {
buf := bytes.NewBuffer(signedPayload)
req, err := http.NewRequest("POST", "http+unix://c/sendsignedtx", buf)
if err != nil {
return nil, err
}

req.Header.Set("c11n-to", strings.Join(b64To, ","))
req.Header.Set("Content-Type", "application/octet-stream")
res, err := c.httpClient.Do(req)

if res != nil {
defer res.Body.Close()
}
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("Non-200 status code: %+v", res)
}

return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body))
}

func (c *Client) ReceivePayload(key []byte) ([]byte, error) {
req, err := http.NewRequest("GET", "http+unix://c/receiveraw", nil)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions private/private.go
Expand Up @@ -8,6 +8,7 @@ import (

type PrivateTransactionManager interface {
Send(data []byte, from string, to []string) ([]byte, error)
SendSignedTx(data []byte, to []string) ([]byte, error)
Receive(data []byte) ([]byte, error)
}

Expand Down