merge with quorum upstream master

This commit is contained in:
amalraj.manigmail.com 2018-10-09 16:01:11 +08:00
commit fcfc7b6d45
13 changed files with 444 additions and 46 deletions

View File

@ -26,9 +26,12 @@ The above diagram is a high-level overview of the privacy architecture used by Q
The quickest way to get started with Quorum is by following instructions in the [Quorum Examples](https://github.com/jpmorganchase/quorum-examples) repository. This allows you to quickly create a network of Quorum nodes, and includes a step-by-step demonstration of the privacy features of Quorum.
## Further Reading
Further documentation can be found in the [docs](docs/) folder and on the [wiki](https://github.com/jpmorganchase/quorum/wiki).
## Official Docker Containers
The official docker containers can be found under https://hub.docker.com/u/quorumengineering/
## See also
* [Quorum](https://github.com/jpmorganchase/quorum): this repository

View File

@ -255,3 +255,17 @@ func RegisterRaftService(stack *node.Node, ctx *cli.Context, cfg gethConfig, eth
}
}
// quorumValidateConsensus checks if a consensus was used. The node is killed if consensus was not used
func quorumValidateConsensus(stack *node.Node, isRaft bool) {
var ethereum *eth.Ethereum
err := stack.Service(&ethereum)
if err != nil {
utils.Fatalf("Error retrieving Ethereum service: %v", err)
}
if !isRaft && ethereum.ChainConfig().Istanbul == nil && ethereum.ChainConfig().Clique == nil {
utils.Fatalf("Consensus not specified. Exiting!!")
}
}

View File

@ -18,6 +18,7 @@ package main
import (
"crypto/rand"
"io/ioutil"
"math/big"
"os"
"path/filepath"
@ -31,18 +32,52 @@ import (
)
const (
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
nodeKey = "b68c0338aa4b266bf38ebe84c6199ae9fac8b29f32998b3ed2fbeafebe8d65c9"
)
var genesis = `{
"config": {
"chainId": 2017,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"istanbul": {
"epoch": 30000,
"policy": 0
}
},
"nonce": "0x0",
"timestamp": "0x0",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"491937757d1b26e29c507b8d4c0b233c2747e68d": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
`
// Tests that a node embedded within a console can be started up properly and
// then terminated by closing the input stream.
func TestConsoleWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
// Start a geth console, make sure it's cleaned up and terminate the console
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--shh",
"console")
@ -72,19 +107,22 @@ at block: 0 ({{niltime}})
// Tests that a console can be attached to a running node via various means.
func TestIPCAttachWelcome(t *testing.T) {
// Configure the instance for IPC attachement
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
var ipc string
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
if runtime.GOOS == "windows" {
ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999))
} else {
ws := tmpdir(t)
defer os.RemoveAll(ws)
ipc = filepath.Join(ws, "geth.ipc")
ipc = filepath.Join(datadir, "geth.ipc")
}
// Note: we need --shh because testAttachWelcome checks for default
// list of ipc modules and shh is included there.
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -95,10 +133,14 @@ func TestIPCAttachWelcome(t *testing.T) {
}
func TestHTTPAttachWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--rpc", "--rpcport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -109,11 +151,14 @@ func TestHTTPAttachWelcome(t *testing.T) {
}
func TestWSAttachWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--ws", "--wsport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -161,3 +206,26 @@ func trulyRandInt(lo, hi int) int {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo)))
return int(num.Int64()) + lo
}
// setupIstanbul creates a temporary directory and copies nodekey and genesis.json.
// It initializes istanbul by calling geth init
func setupIstanbul(t *testing.T) string {
datadir := tmpdir(t)
gethPath := filepath.Join(datadir, "geth")
os.Mkdir(gethPath, 0700)
// Initialize the data directory with the custom genesis block
json := filepath.Join(datadir, "genesis.json")
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
t.Fatalf("failed to write genesis file: %v", err)
}
nodeKeyFile := filepath.Join(gethPath, "nodekey")
if err := ioutil.WriteFile(nodeKeyFile, []byte(nodeKey), 0600); err != nil {
t.Fatalf("failed to write nodekey file: %v", err)
}
runGeth(t, "--datadir", datadir, "init", json).WaitExit()
return datadir
}

View File

@ -257,6 +257,10 @@ func main() {
func geth(ctx *cli.Context) error {
node := makeFullNode(ctx)
startNode(ctx, node)
// Check if a valid consensus is used
quorumValidateConsensus(node, ctx.GlobalBool(utils.RaftModeFlag.Name))
node.Wait()
return nil
}

View File

@ -0,0 +1 @@
package core

View File

@ -592,8 +592,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
return ErrNonceTooLow
}
// Ether value is not currently supported on private transactions
if tx.IsPrivate() && (tx.Value().Sign() != 0) {
return ErrEtherValueUnsupported;
if tx.IsPrivate() && (len(tx.Data()) == 0 || tx.Value().Sign() != 0) {
return ErrEtherValueUnsupported
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL

View File

@ -19,8 +19,14 @@ package core
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"math/big"
"math/rand"
"os"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -28,11 +34,6 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"math/rand"
"time"
"io/ioutil"
"os"
"reflect"
)
// testTxPoolConfig is a transaction pool configuration without stateful disk
@ -307,6 +308,54 @@ func TestQuorumInvalidTransactions(t *testing.T) {
}
func TestValidateTx_whenValueZeroTransferForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
zeroValue := common.Big0
zeroGasPrice := common.Big0
defaultTxPoolGasLimit := big.NewInt(1000000)
arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, zeroValue, defaultTxPoolGasLimit, zeroGasPrice, nil), types.HomesteadSigner{}, key)
arbitraryTx.SetPrivate()
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected:", ErrEtherValueUnsupported, "; got:", err)
}
}
func TestValidateTx_whenValueNonZeroTransferForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
arbitraryValue := common.Big3
arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, nil, key)
pool.currentState.AddBalance(from, balance)
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err)
}
}
func newPrivateTransaction(value *big.Int, data []byte, key *ecdsa.PrivateKey) (*types.Transaction, *big.Int, common.Address) {
zeroGasPrice := common.Big0
defaultTxPoolGasLimit := big.NewInt(1000000)
arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, value, defaultTxPoolGasLimit, zeroGasPrice, data), types.HomesteadSigner{}, key)
arbitraryTx.SetPrivate()
balance := new(big.Int).Add(arbitraryTx.Value(), new(big.Int).Mul(arbitraryTx.Gas(), arbitraryTx.GasPrice()))
from, _ := deriveSender(arbitraryTx)
return arbitraryTx, balance, from
}
func TestValidateTx_whenValueNonZeroWithSmartContractForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
arbitraryValue := common.Big3
arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, []byte("arbitrary bytecode"), key)
pool.currentState.AddBalance(from, balance)
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err)
}
}
func TestTransactionQueue(t *testing.T) {
t.Parallel()
@ -1909,9 +1958,9 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) {
//Checks that the EIP155 signer is assigned to the TxPool no matter the configuration, even invalid config
func TestEIP155SignerOnTxPool(t *testing.T) {
var flagtests = []struct {
name string
homesteadBlock *big.Int
eip155Block *big.Int
name string
homesteadBlock *big.Int
eip155Block *big.Int
}{
{"hsnileip155nil", nil, nil},
{"hsnileip1550", nil, big.NewInt(0)},
@ -1949,4 +1998,3 @@ func TestEIP155SignerOnTxPool(t *testing.T) {
}
}

View File

@ -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).
- <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>
- `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<String>` - (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"
}
```

View File

@ -30,6 +30,8 @@ import (
"net/http"
"github.com/davecgh/go-spew/spew"
"sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
@ -388,11 +390,13 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
if isPrivate {
data := []byte(*args.Data)
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err := private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
if len(data) > 0 {
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err := private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
}
// zekun: HACK
d := hexutil.Bytes(data)
@ -1267,12 +1271,14 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
log.Info("args.data is nil")
}
//Send private transaction to local Constellation node
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
if len(data) > 0 {
//Send private transaction to local Constellation node
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
}
// zekun: HACK
d := hexutil.Bytes(data)
@ -1360,6 +1366,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if err != nil {
return nil, err
}
if args.PrivateFor != nil {
tx.SetPrivate()
}
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return nil, err
@ -1583,6 +1592,11 @@ type AsyncResultFailure struct {
Error string `json:"error"`
}
type Async struct {
sync.Mutex
sem chan struct{}
}
func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSendTxArgs) {
txHash, err := s.SendTransaction(ctx, asyncArgs.SendTxArgs)
@ -1616,6 +1630,14 @@ func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSend
}
func newAsync(n int) *Async {
a := &Async{
sem: make(chan struct{}, n),
}
return a
}
var async = newAsync(100)
// SendTransactionAsync creates a transaction for the given argument, signs it, and
// submits it to the transaction pool. This call returns immediately to allow sending
@ -1629,8 +1651,17 @@ func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSend
// 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) (common.Hash, error){
go s.send(ctx, args)
return common.Hash{}, nil
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

View File

@ -145,7 +145,7 @@ var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
//
// For complete nodes, the node ID is encoded in the username portion
// of the URL, separated from the host by an @ sign. The hostname can
// only be given as an IP address, DNS domain names are not allowed.
// be given as an IP address or a DNS domain name.
// The port in the host name section is the TCP listening port. If the
// TCP and UDP (discovery) ports differ, the UDP port is specified as
// query parameter "discport".
@ -192,7 +192,13 @@ func parseComplete(rawurl string) (*Node, error) {
return nil, fmt.Errorf("invalid host: %v", err)
}
if ip = net.ParseIP(host); ip == nil {
return nil, errors.New("invalid IP address")
// attempt to look up IP addresses if host is a FQDN
lookupIPs, err := net.LookupIP(host)
if err != nil {
return nil, errors.New("invalid IP address")
}
// set to first ip by default
ip = lookupIPs[0]
}
// Ensure the IP is 4 bytes long for IPv4 addresses.
if ipv4 := ip.To4(); ipv4 != nil {

View File

@ -3,6 +3,7 @@ package raft
import (
"sync"
"time"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core"
@ -32,6 +33,7 @@ type RaftService struct {
// we need an event mux to instantiate the blockchain
eventMux *event.TypeMux
minter *minter
nodeKey *ecdsa.PrivateKey
}
func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*discover.Node, datadir string) (*RaftService, error) {
@ -43,6 +45,7 @@ func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raft
accountManager: e.AccountManager(),
downloader: e.Downloader(),
startPeers: startPeers,
nodeKey: ctx.NodeKey(),
}
service.minter = newMinter(chainConfig, service, blockTime)

View File

@ -25,16 +25,22 @@ import (
"github.com/eapache/channels"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
var (
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for arbitrary signer vanity
)
// Current state information for building the next block
@ -50,7 +56,7 @@ type minter struct {
config *params.ChainConfig
mu sync.Mutex
mux *event.TypeMux
eth miner.Backend
eth *RaftService
chain *core.BlockChain
chainDb ethdb.Database
coinbase common.Address
@ -66,6 +72,11 @@ type minter struct {
txPreSub event.Subscription
}
type extraSeal struct {
RaftId []byte // RaftID of the block minter
Signature []byte // Signature of the block minter
}
func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) *minter {
minter := &minter{
config: config,
@ -318,8 +329,6 @@ func (minter *minter) mintNewBlock() {
ethash.AccumulateRewards(minter.chain.Config(), work.publicState, header, nil)
header.Root = work.publicState.IntermediateRoot(minter.chain.Config().IsEIP158(work.header.Number))
// NOTE: < QuorumChain creates a signature here and puts it in header.Extra. >
allReceipts := append(publicReceipts, privateReceipts...)
header.Bloom = types.CreateBloom(allReceipts)
@ -330,6 +339,14 @@ func (minter *minter) mintNewBlock() {
l.BlockHash = headerHash
}
//Sign the block and build the extraSeal struct
extraSealBytes := minter.buildExtraSeal(headerHash)
// add vanity and seal to header
// NOTE: leaving vanity blank for now as a space for any future data
header.Extra = make([]byte, extraVanity+len(extraSealBytes))
copy(header.Extra[extraVanity:], extraSealBytes)
block := types.NewBlock(header, committedTxes, nil, publicReceipts)
log.Info("Generated next block", "block num", block.Number(), "num txes", txCount)
@ -407,3 +424,29 @@ func (env *work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, g
return publicReceipt, privateReceipt, nil
}
func (minter *minter) buildExtraSeal(headerHash common.Hash) []byte {
//Sign the headerHash
nodeKey := minter.eth.nodeKey
sig, err := crypto.Sign(headerHash.Bytes(), nodeKey)
if err != nil {
log.Warn("Block sealing failed", "err", err)
}
//build the extraSeal struct
raftIdString := hexutil.EncodeUint64(uint64(minter.eth.raftProtocolManager.raftId))
var extra extraSeal
extra = extraSeal{
RaftId: []byte(raftIdString[2:]), //remove the 0x prefix
Signature: sig,
}
//encode to byte array for storage
extraDataBytes, err := rlp.EncodeToBytes(extra)
if err != nil {
log.Warn("Header.Extra Data Encoding failed", "err", err)
}
return extraDataBytes
}

69
raft/minter_test.go Normal file
View File

@ -0,0 +1,69 @@
package raft
import (
"testing"
"math/big"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func TestSignHeader(t *testing.T){
//create only what we need to test the seal
var testRaftId uint16 = 5
config := &node.Config{Name: "unit-test", DataDir: ""}
nodeKey := config.NodeKey()
raftProtocolManager := &ProtocolManager{raftId:testRaftId}
raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager}
minter := minter{eth: raftService,}
//create some fake header to sign
fakeParentHash := common.HexToHash("0xc2c1dc1be8054808c69e06137429899d")
header := &types.Header{
ParentHash: fakeParentHash,
Number: big.NewInt(1),
Difficulty: big.NewInt(1),
GasLimit: new(big.Int),
GasUsed: new(big.Int),
Coinbase: minter.coinbase,
Time: big.NewInt(time.Now().UnixNano()),
}
headerHash := header.Hash()
extraDataBytes := minter.buildExtraSeal(headerHash)
var seal *extraSeal
err := rlp.DecodeBytes(extraDataBytes[:], &seal)
if err != nil {
t.Fatalf("Unable to decode seal: %s", err.Error())
}
// Check raftId
sealRaftId, err := hexutil.DecodeUint64("0x"+ string(seal.RaftId)) //add the 0x prefix
if err != nil {
t.Errorf("Unable to get RaftId: %s", err.Error())
}
if sealRaftId != uint64(testRaftId) {
t.Errorf("RaftID does not match. Expected: %d, Actual: %d", testRaftId, sealRaftId)
}
//Identify who signed it
sig:= seal.Signature
pubKey, err := crypto.SigToPub(headerHash.Bytes(), sig)
if err != nil {
t.Fatalf("Unable to get public key from signature: %s", err.Error())
}
//Compare derived public key to original public key
if pubKey.X.Cmp(nodeKey.X) != 0 {
t.Errorf("Signature incorrect!")
}
}