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.
|
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 Reading
|
||||||
|
|
||||||
Further documentation can be found in the [docs](docs/) folder and on the [wiki](https://github.com/jpmorganchase/quorum/wiki).
|
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
|
## See also
|
||||||
|
|
||||||
* [Quorum](https://github.com/jpmorganchase/quorum): this repository
|
* [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 (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -31,18 +32,52 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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"
|
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
|
// Tests that a node embedded within a console can be started up properly and
|
||||||
// then terminated by closing the input stream.
|
// then terminated by closing the input stream.
|
||||||
func TestConsoleWelcome(t *testing.T) {
|
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
|
// Start a geth console, make sure it's cleaned up and terminate the console
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
"--etherbase", coinbase, "--shh",
|
"--etherbase", coinbase, "--shh",
|
||||||
"console")
|
"console")
|
||||||
|
|
||||||
|
@ -72,19 +107,22 @@ at block: 0 ({{niltime}})
|
||||||
// Tests that a console can be attached to a running node via various means.
|
// Tests that a console can be attached to a running node via various means.
|
||||||
func TestIPCAttachWelcome(t *testing.T) {
|
func TestIPCAttachWelcome(t *testing.T) {
|
||||||
// Configure the instance for IPC attachement
|
// Configure the instance for IPC attachement
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
|
||||||
var ipc string
|
var ipc string
|
||||||
|
|
||||||
|
datadir := setupIstanbul(t)
|
||||||
|
defer os.RemoveAll(datadir)
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999))
|
ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999))
|
||||||
} else {
|
} else {
|
||||||
ws := tmpdir(t)
|
ipc = filepath.Join(datadir, "geth.ipc")
|
||||||
defer os.RemoveAll(ws)
|
|
||||||
ipc = filepath.Join(ws, "geth.ipc")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we need --shh because testAttachWelcome checks for default
|
// Note: we need --shh because testAttachWelcome checks for default
|
||||||
// list of ipc modules and shh is included there.
|
// list of ipc modules and shh is included there.
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
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) {
|
func TestHTTPAttachWelcome(t *testing.T) {
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
|
||||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||||
|
|
||||||
|
datadir := setupIstanbul(t)
|
||||||
|
defer os.RemoveAll(datadir)
|
||||||
|
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
"--etherbase", coinbase, "--rpc", "--rpcport", port)
|
"--etherbase", coinbase, "--rpc", "--rpcport", port)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
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) {
|
func TestWSAttachWelcome(t *testing.T) {
|
||||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
|
||||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||||
|
|
||||||
|
datadir := setupIstanbul(t)
|
||||||
|
defer os.RemoveAll(datadir)
|
||||||
|
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||||
"--etherbase", coinbase, "--ws", "--wsport", port)
|
"--etherbase", coinbase, "--ws", "--wsport", port)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
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)))
|
num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo)))
|
||||||
return int(num.Int64()) + 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 {
|
func geth(ctx *cli.Context) error {
|
||||||
node := makeFullNode(ctx)
|
node := makeFullNode(ctx)
|
||||||
startNode(ctx, node)
|
startNode(ctx, node)
|
||||||
|
|
||||||
|
// Check if a valid consensus is used
|
||||||
|
quorumValidateConsensus(node, ctx.GlobalBool(utils.RaftModeFlag.Name))
|
||||||
|
|
||||||
node.Wait()
|
node.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/istanbul"
|
"github.com/ethereum/go-ethereum/consensus/istanbul"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
// HandleMsg implements consensus.Handler.HandleMsg
|
||||||
func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
|
func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
|
||||||
sb.coreMu.Lock()
|
sb.coreMu.Lock()
|
||||||
|
@ -54,13 +63,11 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
|
||||||
return true, istanbul.ErrStoppedEngine
|
return true, istanbul.ErrStoppedEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []byte
|
data, hash, err := sb.decode(msg)
|
||||||
if err := msg.Decode(&data); err != nil {
|
if err != nil {
|
||||||
return true, errDecodeFailed
|
return true, errDecodeFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := istanbul.RLPHash(data)
|
|
||||||
|
|
||||||
// Mark peer's message
|
// Mark peer's message
|
||||||
ms, ok := sb.recentMessages.Get(addr)
|
ms, ok := sb.recentMessages.Get(addr)
|
||||||
var m *lru.ARCCache
|
var m *lru.ARCCache
|
||||||
|
@ -84,6 +91,17 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
|
||||||
|
|
||||||
return true, nil
|
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
|
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
|
v := c.valSet
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return false
|
return false
|
||||||
|
@ -240,7 +240,7 @@ func (c *core) startNewRound(round *big.Int) {
|
||||||
c.valSet.CalcProposer(lastProposer, newView.Round.Uint64())
|
c.valSet.CalcProposer(lastProposer, newView.Round.Uint64())
|
||||||
c.waitingForRoundChange = false
|
c.waitingForRoundChange = false
|
||||||
c.setState(StateAcceptRequest)
|
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 it is locked, propose the old proposal
|
||||||
// If we have pending request, propose pending request
|
// If we have pending request, propose pending request
|
||||||
if c.current.IsHashLocked() {
|
if c.current.IsHashLocked() {
|
||||||
|
@ -254,7 +254,7 @@ func (c *core) startNewRound(round *big.Int) {
|
||||||
}
|
}
|
||||||
c.newRoundChangeTimer()
|
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) {
|
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)
|
logger := c.logger.New("state", c.state)
|
||||||
|
|
||||||
// If I'm the proposer and I have the same sequence with the proposal
|
// 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()
|
curView := c.currentView()
|
||||||
preprepare, err := Encode(&istanbul.Preprepare{
|
preprepare, err := Encode(&istanbul.Preprepare{
|
||||||
View: curView,
|
View: curView,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
type Engine interface {
|
type Engine interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop() error
|
Stop() error
|
||||||
|
IsProposer() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type State uint64
|
type State uint64
|
||||||
|
|
|
@ -585,8 +585,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||||
return ErrNonceTooLow
|
return ErrNonceTooLow
|
||||||
}
|
}
|
||||||
// Ether value is not currently supported on private transactions
|
// Ether value is not currently supported on private transactions
|
||||||
if tx.IsPrivate() && (tx.Value().Sign() != 0) {
|
if tx.IsPrivate() && (len(tx.Data()) == 0 || tx.Value().Sign() != 0) {
|
||||||
return ErrEtherValueUnsupported;
|
return ErrEtherValueUnsupported
|
||||||
}
|
}
|
||||||
// Transactor should have enough funds to cover the costs
|
// Transactor should have enough funds to cover the costs
|
||||||
// cost == V + GP * GL
|
// cost == V + GP * GL
|
||||||
|
|
|
@ -19,8 +19,14 @@ package core
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
@ -28,11 +34,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// testTxPoolConfig is a transaction pool configuration without stateful disk
|
// 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) {
|
func TestTransactionQueue(t *testing.T) {
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
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
|
//Checks that the EIP155 signer is assigned to the TxPool no matter the configuration, even invalid config
|
||||||
func TestEIP155SignerOnTxPool(t *testing.T) {
|
func TestEIP155SignerOnTxPool(t *testing.T) {
|
||||||
var flagtests = []struct {
|
var flagtests = []struct {
|
||||||
name string
|
name string
|
||||||
homesteadBlock *big.Int
|
homesteadBlock *big.Int
|
||||||
eip155Block *big.Int
|
eip155Block *big.Int
|
||||||
}{
|
}{
|
||||||
{"hsnileip155nil", nil, nil},
|
{"hsnileip155nil", nil, nil},
|
||||||
{"hsnileip1550", nil, big.NewInt(0)},
|
{"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)
|
data := []byte(args.Data)
|
||||||
isPrivate := args.PrivateFor != nil
|
isPrivate := args.PrivateFor != nil
|
||||||
if isPrivate {
|
if isPrivate {
|
||||||
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
if len(data) > 0 {
|
||||||
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
|
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
||||||
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
|
||||||
if err != nil {
|
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
||||||
return common.Hash{}, err
|
if err != nil {
|
||||||
}
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
} // else tx_pool.go#validateTx will capture and throw error
|
||||||
args.Data = data
|
args.Data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,13 +1154,15 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
|
||||||
isPrivate := args.PrivateFor != nil
|
isPrivate := args.PrivateFor != nil
|
||||||
|
|
||||||
if isPrivate {
|
if isPrivate {
|
||||||
//Send private transaction to local Constellation node
|
if len(data) > 0 {
|
||||||
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
//Send private transaction to local Constellation node
|
||||||
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
|
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
||||||
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
|
||||||
if err != nil {
|
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
|
||||||
return common.Hash{}, err
|
if err != nil {
|
||||||
}
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
} // else tx_pool.go#validateTx will capture and throw error
|
||||||
args.Data = data
|
args.Data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1239,6 +1243,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if args.PrivateFor != nil {
|
||||||
|
tx.SetPrivate()
|
||||||
|
}
|
||||||
data, err := rlp.EncodeToBytes(tx)
|
data, err := rlp.EncodeToBytes(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
timeFormat = "2006-01-02T15:04:05-0700"
|
timeFormat = "2006-01-02T15:04:05-0700"
|
||||||
termTimeFormat = "01-02|15:04:05"
|
termTimeFormat = "01-02|15:04:05.000"
|
||||||
floatFormat = 'f'
|
floatFormat = 'f'
|
||||||
termMsgJust = 40
|
termMsgJust = 40
|
||||||
)
|
)
|
||||||
|
@ -107,7 +108,7 @@ func TerminalFormat(usecolor bool) Format {
|
||||||
lvl := r.Lvl.AlignedString()
|
lvl := r.Lvl.AlignedString()
|
||||||
if atomic.LoadUint32(&locationEnabled) != 0 {
|
if atomic.LoadUint32(&locationEnabled) != 0 {
|
||||||
// Log origin printing was requested, format the location path and line number
|
// 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 {
|
for _, prefix := range locationTrims {
|
||||||
location = strings.TrimPrefix(location, prefix)
|
location = strings.TrimPrefix(location, prefix)
|
||||||
}
|
}
|
||||||
|
@ -361,3 +362,12 @@ func escapeString(s string) string {
|
||||||
stringBufPool.Put(e)
|
stringBufPool.Put(e)
|
||||||
return ret
|
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
|
// 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
|
// 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
|
// 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
|
// TCP and UDP (discovery) ports differ, the UDP port is specified as
|
||||||
// query parameter "discport".
|
// query parameter "discport".
|
||||||
|
@ -192,7 +192,13 @@ func parseComplete(rawurl string) (*Node, error) {
|
||||||
return nil, fmt.Errorf("invalid host: %v", err)
|
return nil, fmt.Errorf("invalid host: %v", err)
|
||||||
}
|
}
|
||||||
if ip = net.ParseIP(host); ip == nil {
|
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.
|
// Ensure the IP is 4 bytes long for IPv4 addresses.
|
||||||
if ipv4 := ip.To4(); ipv4 != nil {
|
if ipv4 := ip.To4(); ipv4 != nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package raft
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
@ -32,6 +33,7 @@ type RaftService struct {
|
||||||
// we need an event mux to instantiate the blockchain
|
// we need an event mux to instantiate the blockchain
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
minter *minter
|
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) {
|
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(),
|
accountManager: e.AccountManager(),
|
||||||
downloader: e.Downloader(),
|
downloader: e.Downloader(),
|
||||||
startPeers: startPeers,
|
startPeers: startPeers,
|
||||||
|
nodeKey: ctx.NodeKey(),
|
||||||
}
|
}
|
||||||
|
|
||||||
service.minter = newMinter(chainConfig, service, blockTime)
|
service.minter = newMinter(chainConfig, service, blockTime)
|
||||||
|
|
|
@ -25,16 +25,22 @@ import (
|
||||||
|
|
||||||
"github.com/eapache/channels"
|
"github.com/eapache/channels"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"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
|
// Current state information for building the next block
|
||||||
|
@ -50,7 +56,7 @@ type minter struct {
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
mux *event.TypeMux
|
mux *event.TypeMux
|
||||||
eth miner.Backend
|
eth *RaftService
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
chainDb ethdb.Database
|
chainDb ethdb.Database
|
||||||
coinbase common.Address
|
coinbase common.Address
|
||||||
|
@ -66,6 +72,11 @@ type minter struct {
|
||||||
txPreSub event.Subscription
|
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 {
|
func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) *minter {
|
||||||
minter := &minter{
|
minter := &minter{
|
||||||
config: config,
|
config: config,
|
||||||
|
@ -318,8 +329,6 @@ func (minter *minter) mintNewBlock() {
|
||||||
ethash.AccumulateRewards(minter.chain.Config(), work.publicState, header, nil)
|
ethash.AccumulateRewards(minter.chain.Config(), work.publicState, header, nil)
|
||||||
header.Root = work.publicState.IntermediateRoot(minter.chain.Config().IsEIP158(work.header.Number))
|
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...)
|
allReceipts := append(publicReceipts, privateReceipts...)
|
||||||
header.Bloom = types.CreateBloom(allReceipts)
|
header.Bloom = types.CreateBloom(allReceipts)
|
||||||
|
|
||||||
|
@ -330,6 +339,14 @@ func (minter *minter) mintNewBlock() {
|
||||||
l.BlockHash = headerHash
|
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)
|
block := types.NewBlock(header, committedTxes, nil, publicReceipts)
|
||||||
|
|
||||||
log.Info("Generated next block", "block num", block.Number(), "num txes", txCount)
|
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
|
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