From 40f7d2b8b4ca772b69c2d35e03eabbfd2b96bb67 Mon Sep 17 00:00:00 2001 From: Nguyen Kien Trung Date: Mon, 1 Oct 2018 14:37:05 -0400 Subject: [PATCH 1/6] Value Transfer in Private Transactions (#538) Fix #528 --- core/tx_pool.go | 4 +-- core/tx_pool_test.go | 66 ++++++++++++++++++++++++++++++++++++------ internal/ethapi/api.go | 33 ++++++++++++--------- 3 files changed, 79 insertions(+), 24 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 55c7405af..1cac19a83 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -585,8 +585,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 diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 84c5dc617..73fe320bd 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -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 @@ -281,6 +282,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) { pool, key := setupTxPool() defer pool.Stop() @@ -1527,9 +1576,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)}, @@ -1567,4 +1616,3 @@ func TestEIP155SignerOnTxPool(t *testing.T) { } } - diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 65a644e22..4c2a1fff8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -362,12 +362,14 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs data := []byte(args.Data) isPrivate := args.PrivateFor != nil if isPrivate { - 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 + } + } // else tx_pool.go#validateTx will capture and throw error args.Data = data } @@ -1152,13 +1154,15 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen isPrivate := args.PrivateFor != nil if isPrivate { - //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 + } + } // else tx_pool.go#validateTx will capture and throw error args.Data = data } @@ -1239,6 +1243,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 From 2ad2e0b4bf016dc7dafd494fcdfa82a8aff3d1cf Mon Sep 17 00:00:00 2001 From: Jitendra Bhurat Date: Mon, 1 Oct 2018 16:39:01 -0400 Subject: [PATCH 2/6] =?UTF-8?q?Added=20a=20validateConsensus()=20which=20e?= =?UTF-8?q?xits=20geth=20if=20no=20consensus=20is=20speci=E2=80=A6=20(#540?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate for a Quorum supported consensus and exit if no consensus is specified. --- cmd/geth/config.go | 14 ++++++ cmd/geth/consolecmd_test.go | 92 ++++++++++++++++++++++++++++++++----- cmd/geth/main.go | 4 ++ 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 4437cc946..cd634aecf 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -264,3 +264,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(ðereum) + 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!!") + } +} diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 258b9e6dd..01b2f4576 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -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 +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index bace336bf..7c4c23684 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -216,6 +216,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 } From 05f6cf9bb2ea29bd4b450a7c538f7877b620b941 Mon Sep 17 00:00:00 2001 From: Poh Zi How Date: Wed, 3 Oct 2018 04:38:20 +0800 Subject: [PATCH 3/6] look up IP if host is FQDN (#544) Fixes #147 --- p2p/discover/node.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 711b940e7..0cdadc49a 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -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 { From db8cc814fed929d3e396cd508310e5e1927c1e43 Mon Sep 17 00:00:00 2001 From: dbryan0516 Date: Wed, 3 Oct 2018 20:14:48 -0400 Subject: [PATCH 4/6] Raft Block Signature (#395) Added block signature to raft in the header.Extra field --- raft/backend.go | 3 ++ raft/minter.go | 51 ++++++++++++++++++++++++++++++--- raft/minter_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 raft/minter_test.go diff --git a/raft/backend.go b/raft/backend.go index a9e328fa1..dc3bd0bac 100644 --- a/raft/backend.go +++ b/raft/backend.go @@ -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) diff --git a/raft/minter.go b/raft/minter.go index 5ece9e389..7bb7c2b2a 100644 --- a/raft/minter.go +++ b/raft/minter.go @@ -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 +} diff --git a/raft/minter_test.go b/raft/minter_test.go new file mode 100644 index 000000000..dd60d4746 --- /dev/null +++ b/raft/minter_test.go @@ -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!") + } + +} From 581eed5a0f4776e51b9d0b12fac6a1f9188de4d7 Mon Sep 17 00:00:00 2001 From: fixanoid Date: Fri, 5 Oct 2018 11:39:41 -0400 Subject: [PATCH 5/6] Adding docker hub link --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 17665240b..0e7515c39 100644 --- a/README.md +++ b/README.md @@ -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 From 478139017dc16a8d9b5cc66d4587b04b637502e0 Mon Sep 17 00:00:00 2001 From: Trung Nguyen Date: Fri, 5 Oct 2018 16:09:10 -0400 Subject: [PATCH 6/6] fix racing condition when proposer receives same block from peer --- consensus/istanbul/backend/handler.go | 28 ++++++++++++++++++++++----- consensus/istanbul/core/core.go | 6 +++--- consensus/istanbul/core/preprepare.go | 2 +- consensus/istanbul/core/types.go | 1 + log/format.go | 14 ++++++++++++-- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/consensus/istanbul/backend/handler.go b/consensus/istanbul/backend/handler.go index a33800945..57fd82279 100644 --- a/consensus/istanbul/backend/handler.go +++ b/consensus/istanbul/backend/handler.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/istanbul" "github.com/ethereum/go-ethereum/p2p" - lru "github.com/hashicorp/golang-lru" + "github.com/hashicorp/golang-lru" ) const ( @@ -44,6 +44,15 @@ func (sb *backend) Protocol() consensus.Protocol { } } +func (sb *backend) decode(msg p2p.Msg) ([]byte, common.Hash, error) { + var data []byte + if err := msg.Decode(&data); err != nil { + return nil, common.Hash{}, errDecodeFailed + } + + return data, istanbul.RLPHash(data), nil +} + // HandleMsg implements consensus.Handler.HandleMsg func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) { sb.coreMu.Lock() @@ -54,13 +63,11 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) { return true, istanbul.ErrStoppedEngine } - var data []byte - if err := msg.Decode(&data); err != nil { + data, hash, err := sb.decode(msg) + if err != nil { return true, errDecodeFailed } - hash := istanbul.RLPHash(data) - // Mark peer's message ms, ok := sb.recentMessages.Get(addr) var m *lru.ARCCache @@ -84,6 +91,17 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) { return true, nil } + if msg.Code == 0x07 && sb.core.IsProposer() { // eth.NewBlockMsg: import cycle + // this case is to safeguard the race of similar block which gets propagated from other node while this node is proposing + + _, hash, err := sb.decode(msg) + if err != nil { + return true, errDecodeFailed + } + if _, ok := sb.knownMessages.Get(hash); ok { + return true, nil + } + } return false, nil } diff --git a/consensus/istanbul/core/core.go b/consensus/istanbul/core/core.go index 0cede30ee..59bee199e 100644 --- a/consensus/istanbul/core/core.go +++ b/consensus/istanbul/core/core.go @@ -152,7 +152,7 @@ func (c *core) currentView() *istanbul.View { } } -func (c *core) isProposer() bool { +func (c *core) IsProposer() bool { v := c.valSet if v == nil { return false @@ -240,7 +240,7 @@ func (c *core) startNewRound(round *big.Int) { c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) c.waitingForRoundChange = false c.setState(StateAcceptRequest) - if roundChange && c.isProposer() && c.current != nil { + if roundChange && c.IsProposer() && c.current != nil { // If it is locked, propose the old proposal // If we have pending request, propose pending request if c.current.IsHashLocked() { @@ -254,7 +254,7 @@ func (c *core) startNewRound(round *big.Int) { } c.newRoundChangeTimer() - logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "isProposer", c.isProposer()) + logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "IsProposer", c.IsProposer()) } func (c *core) catchUpRound(view *istanbul.View) { diff --git a/consensus/istanbul/core/preprepare.go b/consensus/istanbul/core/preprepare.go index a9e594967..a4ee295b7 100644 --- a/consensus/istanbul/core/preprepare.go +++ b/consensus/istanbul/core/preprepare.go @@ -27,7 +27,7 @@ func (c *core) sendPreprepare(request *istanbul.Request) { logger := c.logger.New("state", c.state) // If I'm the proposer and I have the same sequence with the proposal - if c.current.Sequence().Cmp(request.Proposal.Number()) == 0 && c.isProposer() { + if c.current.Sequence().Cmp(request.Proposal.Number()) == 0 && c.IsProposer() { curView := c.currentView() preprepare, err := Encode(&istanbul.Preprepare{ View: curView, diff --git a/consensus/istanbul/core/types.go b/consensus/istanbul/core/types.go index 74e1b2263..71e388521 100644 --- a/consensus/istanbul/core/types.go +++ b/consensus/istanbul/core/types.go @@ -27,6 +27,7 @@ import ( type Engine interface { Start() error Stop() error + IsProposer() bool } type State uint64 diff --git a/log/format.go b/log/format.go index 0b07abb2a..11cdb3788 100644 --- a/log/format.go +++ b/log/format.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "reflect" + "runtime" "strconv" "strings" "sync" @@ -15,7 +16,7 @@ import ( const ( timeFormat = "2006-01-02T15:04:05-0700" - termTimeFormat = "01-02|15:04:05" + termTimeFormat = "01-02|15:04:05.000" floatFormat = 'f' termMsgJust = 40 ) @@ -107,7 +108,7 @@ func TerminalFormat(usecolor bool) Format { lvl := r.Lvl.AlignedString() if atomic.LoadUint32(&locationEnabled) != 0 { // Log origin printing was requested, format the location path and line number - location := fmt.Sprintf("%+v", r.Call) + location := fmt.Sprintf("%+v|%v", r.Call, getGID()) for _, prefix := range locationTrims { location = strings.TrimPrefix(location, prefix) } @@ -361,3 +362,12 @@ func escapeString(s string) string { stringBufPool.Put(e) return ret } + +func getGID() uint64 { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + n, _ := strconv.ParseUint(string(b), 10, 64) + return n +} \ No newline at end of file