mirror of https://github.com/poanetwork/quorum.git
fix impossible reorg
This commit is contained in:
commit
4e22d13b10
|
@ -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
|
||||
|
|
|
@ -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!!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
type Engine interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
IsProposer() bool
|
||||
}
|
||||
|
||||
type State uint64
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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!")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue