Merge pull request #50 from ngtuna/voting-smc

Get masternodes candidates from smart contract
This commit is contained in:
Tuna 2018-07-03 16:51:52 +07:00 committed by GitHub
commit d6c6163b8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 58 deletions

View file

@ -26,9 +26,13 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
validatorContract "github.com/ethereum/go-ethereum/contracts/validator/contract"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
@ -313,23 +317,25 @@ func startNode(ctx *cli.Context, stack *node.Node) {
started = true started = true
log.Info("Enabled mining node!!!") log.Info("Enabled mining node!!!")
} }
defer close(core.Checkpoint) defer close(core.CheckpointCh)
defer close(core.M1Ch)
for range core.Checkpoint { for {
select {
case <-core.CheckpointCh:
log.Info("Checkpoint!!! It's time to reconcile node's state...") log.Info("Checkpoint!!! It's time to reconcile node's state...")
ok, err := ethereum.ValidateStaker() ok, err := ethereum.ValidateStaker()
if err != nil { if err != nil {
utils.Fatalf("Can't verify validator permission: %v", err) utils.Fatalf("Can't verify masternode permission: %v", err)
} }
if !ok { if !ok {
log.Info("Only validator can mine blocks. Cancelling mining on this node...") log.Info("Only masternode can propose and verify blocks. Cancelling mining on this node...")
if started { if started {
ethereum.StopMining() ethereum.StopMining()
started = false started = false
} }
log.Info("Cancelled mining mode!!!") log.Info("Cancelled mining mode!!!")
} else if !started { } else if !started {
log.Info("Validator found. Enabling mining mode...") log.Info("Masternode found. Enabling mining mode...")
// Use a reduced number of threads if requested // Use a reduced number of threads if requested
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
type threaded interface { type threaded interface {
@ -347,6 +353,55 @@ func startNode(ctx *cli.Context, stack *node.Node) {
started = true started = true
log.Info("Enabled mining node!!!") log.Info("Enabled mining node!!!")
} }
case <-core.M1Ch:
log.Info("It's time to update new set of masternodes for the next epoch...")
// get masternodes information from smart contract
client, err := ethclient.Dial(stack.IPCEndpoint())
if err != nil {
utils.Fatalf("Fail to connect RPC: %v", err)
}
addr := common.HexToAddress(common.Validator)
validator, err := validatorContract.NewTomoValidator(addr, client)
if err != nil {
utils.Fatalf("Fail to get validator smc: %v", err)
}
opts := new(bind.CallOpts)
candidates, err := validator.GetCandidates(opts)
if err != nil {
utils.Fatalf("Can't get list of masternode candidates: %v", err)
}
var ms []clique.Masternode
for _, candidate := range candidates {
v, err := validator.GetCandidateCap(opts, candidate)
if err != nil {
log.Warn("Can't get cap of a masternode candidate. Will ignore him", "address", candidate, "error", err)
}
//TODO: smart contract shouldn't return "0x0000000000000000000000000000000000000000"
if candidate.String() != "0x0000000000000000000000000000000000000000" {
ms = append(ms, clique.Masternode{Address: candidate, Stake: v.String()})
}
}
//// order by cap
//sort.Slice(ms, func(i, j int) bool {
// return ms[i].Stake > ms[j].Stake
//})
log.Info("Ordered list of masternode candidates")
for _, m := range ms {
fmt.Printf("address: %s, stake: %s\n", m.Address.String(), m.Stake)
}
if len(ms) == 0 {
log.Info("No masternode candidates found. Keep the current masternodes set for the next epoch")
} else {
// update masternodes
log.Info("Updating new set of masternodes")
err = ethereum.UpdateMasternodes(ms)
if err != nil {
utils.Fatalf("Can't update masternodes: %v", err)
}
log.Info("Masternodes are ready for the next epoch")
}
}
} }
}() }()
} }

View file

@ -31,6 +31,7 @@ const (
HashLength = 32 HashLength = 32
AddressLength = 20 AddressLength = 20
BlockSigners = "0x0000000000000000000000000000000000000089" BlockSigners = "0x0000000000000000000000000000000000000089"
Validator = "0x0000000000000000000000000000000000000088"
) )
var ( var (

View file

@ -25,6 +25,7 @@ import (
"sync" "sync"
"time" "time"
"fmt"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -46,11 +47,15 @@ const (
checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory
inmemorySignatures = 4096 // Number of recent block signatures to keep in memory inmemorySignatures = 4096 // Number of recent block signatures to keep in memory
wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
genesisCoinBase = "0x0000000000000000000000000000000000000000"
) )
type Masternode struct {
Address common.Address
Stake string
}
// Clique proof-of-authority protocol constants. // Clique proof-of-authority protocol constants.
var ( var (
epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes
@ -377,6 +382,10 @@ func (c *Clique) GetSnapshot(chain consensus.ChainReader, header *types.Header)
return snap, nil return snap, nil
} }
func (c *Clique) StoreSnapshot(snap *Snapshot) error {
return snap.store(c.db)
}
func position(list []common.Address, x common.Address) int { func position(list []common.Address, x common.Address) int {
for i, item := range list { for i, item := range list {
if item == x { if item == x {
@ -386,7 +395,17 @@ func position(list []common.Address, x common.Address) int {
return -1 return -1
} }
func YourTurn(snap *Snapshot, header *types.Header, cur common.Address) (bool, error) { func (c *Clique) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
lastCheckpointNumber := header.Number.Uint64() - (header.Number.Uint64() % c.config.Epoch)
preCheckpointHeader := chain.GetHeaderByNumber(lastCheckpointNumber)
masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(masternodes); i++ {
copy(masternodes[i][:], preCheckpointHeader.Extra[extraVanity+i*common.AddressLength:])
}
return masternodes
}
func YourTurn(masternodes []common.Address, snap *Snapshot, header *types.Header, cur common.Address) (bool, error) {
if header.Number.Uint64() == 0 { if header.Number.Uint64() == 0 {
// Not check signer for genesis block. // Not check signer for genesis block.
return true, nil return true, nil
@ -396,10 +415,13 @@ func YourTurn(snap *Snapshot, header *types.Header, cur common.Address) (bool, e
if err != nil { if err != nil {
return false, err return false, err
} }
preIndex := position(snap.signers(), pre) preIndex := position(masternodes, pre)
curIndex := position(snap.signers(), cur) curIndex := position(masternodes, cur)
log.Info("Debugging info", "number of masternodes", len(snap.signers()), "previous", pre, "position", preIndex, "current", cur, "position", curIndex) log.Info("Debugging info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", cur, "position", curIndex)
return (preIndex+1)%len(snap.signers()) == curIndex, nil for i, s := range masternodes {
fmt.Printf("%d - %s\n", i, s.String())
}
return (preIndex+1)%len(masternodes) == curIndex, nil
} }
// snapshot retrieves the authorization snapshot at a given point in time. // snapshot retrieves the authorization snapshot at a given point in time.
@ -516,8 +538,18 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p
return err return err
} }
if _, ok := snap.Signers[signer]; !ok { if _, ok := snap.Signers[signer]; !ok {
valid := false
masternodes := c.GetMasternodes(chain, header)
for _, m := range masternodes {
if m == signer {
valid = true
break
}
}
if !valid {
return errUnauthorized return errUnauthorized
} }
}
for seen, recent := range snap.Recents { for seen, recent := range snap.Recents {
if recent == signer { if recent == signer {
// Signer is among recents, only fail if the current block doesn't shift it out // Signer is among recents, only fail if the current block doesn't shift it out
@ -602,6 +634,29 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
return nil return nil
} }
func (c *Clique) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []Masternode) error {
number := header.Number.Uint64()
log.Trace("take snapshot", "number", number, "hash", header.Hash())
snap, err := c.snapshot(chain, number, header.Hash(), nil)
if err != nil {
return err
}
currentSigners := snap.signers()
proposedSigners := make(map[common.Address]struct{})
// count all addresses in ms to be masternode
for _, m := range ms {
proposedSigners[m.Address] = struct{}{}
c.proposals[m.Address] = true
}
// deactivate current masternodes which aren't in ms
for _, s := range currentSigners {
if _, ok := proposedSigners[s]; !ok {
c.proposals[s] = false
}
}
return nil
}
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given, and returns the final block. // rewards given, and returns the final block.
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
@ -659,8 +714,18 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
return nil, err return nil, err
} }
if _, authorized := snap.Signers[signer]; !authorized { if _, authorized := snap.Signers[signer]; !authorized {
valid := false
masternodes := c.GetMasternodes(chain, header)
for _, m := range masternodes {
if m == signer {
valid = true
break
}
}
if !valid {
return nil, errUnauthorized return nil, errUnauthorized
} }
}
// If we're amongst the recent signers, wait for the next block // If we're amongst the recent signers, wait for the next block
for seen, recent := range snap.Recents { for seen, recent := range snap.Recents {
if recent == signer { if recent == signer {

View file

@ -205,14 +205,15 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, ok := snap.Signers[signer]; !ok { //FIXME: skip signer checking at this step until a good solution found
return nil, errUnauthorized //if _, ok := snap.Signers[signer]; !ok {
} // return nil, errUnauthorized
for _, recent := range snap.Recents { //}
if recent == signer { //for _, recent := range snap.Recents {
return nil, errUnauthorized // if recent == signer {
} // return nil, errUnauthorized
} // }
//}
snap.Recents[number] = signer snap.Recents[number] = signer
// Header authorized, discard any previous votes from the signer // Header authorized, discard any previous votes from the signer

View file

@ -47,7 +47,8 @@ import (
var ( var (
blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
Checkpoint = make(chan int) CheckpointCh = make(chan int)
M1Ch = make(chan int)
ErrNoGenesis = errors.New("Genesis not found in chain") ErrNoGenesis = errors.New("Genesis not found in chain")
) )
@ -61,6 +62,7 @@ const (
// BlockChainVersion ensures that an incompatible database forces a resync from scratch. // BlockChainVersion ensures that an incompatible database forces a resync from scratch.
BlockChainVersion = 3 BlockChainVersion = 3
M1Gap = 3
) )
// CacheConfig contains the configuration values for the trie caching/pruning // CacheConfig contains the configuration values for the trie caching/pruning
@ -1185,9 +1187,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
stats.processed++ stats.processed++
stats.usedGas += usedGas stats.usedGas += usedGas
stats.report(chain, i, bc.stateCache.TrieDB().Size()) stats.report(chain, i, bc.stateCache.TrieDB().Size())
if i == len(chain)-1 { if i == len(chain)-1 && bc.chainConfig.Clique != nil {
if (bc.chainConfig.Clique != nil) && (chain[i].NumberU64()%bc.chainConfig.Clique.Epoch) == 0 { // epoch block
Checkpoint <- 1 if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == 0 {
CheckpointCh <- 1
}
// prepare set of masternodes for the next epoch
if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == (bc.chainConfig.Clique.Epoch - M1Gap) {
M1Ch <- 1
} }
} }
} }

View file

@ -53,6 +53,8 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
const NumOfMasternodes = 99
type LesServer interface { type LesServer interface {
Start(srvr *p2p.Server) Start(srvr *p2p.Server)
Stop() Stop()
@ -236,7 +238,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
} }
} }
} }
return nil return nil
} }
} }
@ -420,6 +421,20 @@ func (s *Ethereum) ValidateStaker() (bool, error) {
return true, nil return true, nil
} }
// Store new set of masternodes into local db
func (s *Ethereum) UpdateMasternodes(ms []clique.Masternode) error {
// get snapshot from local db
if s.chainConfig.Clique == nil {
return errors.New("not clique")
}
c := s.engine.(*clique.Clique)
err := c.UpdateMasternodes(s.blockchain, s.blockchain.CurrentHeader(), ms)
if err != nil {
return err
}
return nil
}
func (s *Ethereum) StartStaking(local bool) error { func (s *Ethereum) StartStaking(local bool) error {
eb, err := s.Etherbase() eb, err := s.Etherbase()
if err != nil { if err != nil {

View file

@ -348,9 +348,19 @@ func (self *worker) wait() {
return return
} }
if _, authorized := snap.Signers[self.coinbase]; !authorized { if _, authorized := snap.Signers[self.coinbase]; !authorized {
valid := false
masternodes := c.GetMasternodes(self.chain, block.Header())
for _, m := range masternodes {
if m == self.coinbase {
valid = true
break
}
}
if !valid {
log.Error("Coinbase address not in snapshot signers.") log.Error("Coinbase address not in snapshot signers.")
return return
} }
}
// Send tx sign to smart contract blockSigners. // Send tx sign to smart contract blockSigners.
if err := contracts.CreateTransactionSign(self.config, self.eth.TxPool(), self.eth.AccountManager(), block); err != nil { if err := contracts.CreateTransactionSign(self.config, self.eth.TxPool(), self.eth.AccountManager(), block); err != nil {
log.Error("Fail to create tx sign for signer", "error", "err") log.Error("Fail to create tx sign for signer", "error", "err")
@ -421,13 +431,15 @@ func (self *worker) commitNewWork() {
// check if we are right after parent's coinbase in the list // check if we are right after parent's coinbase in the list
// only go with Clique // only go with Clique
if self.config.Clique != nil { if self.config.Clique != nil {
// get masternodes set from latest checkpoint
c := self.engine.(*clique.Clique) c := self.engine.(*clique.Clique)
masternodes := c.GetMasternodes(self.chain, parent.Header())
snap, err := c.GetSnapshot(self.chain, parent.Header()) snap, err := c.GetSnapshot(self.chain, parent.Header())
if err != nil { if err != nil {
log.Error("Failed when trying to commit new work", "err", err) log.Error("Failed when trying to commit new work", "err", err)
return return
} }
ok, err := clique.YourTurn(snap, parent.Header(), self.coinbase) ok, err := clique.YourTurn(masternodes, snap, parent.Header(), self.coinbase)
if err != nil { if err != nil {
log.Error("Failed when trying to commit new work", "err", err) log.Error("Failed when trying to commit new work", "err", err)
return return
@ -530,8 +542,15 @@ func (self *worker) commitNewWork() {
log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
self.unconfirmed.Shift(work.Block.NumberU64() - 1) self.unconfirmed.Shift(work.Block.NumberU64() - 1)
} }
if (work.config.Clique != nil) && (work.Block.NumberU64()%work.config.Clique.Epoch) == 0 { if work.config.Clique != nil {
core.Checkpoint <- 1 // epoch block
if (work.Block.NumberU64() % work.config.Clique.Epoch) == 0 {
core.CheckpointCh <- 1
}
// prepare set of masternodes for the next epoch
if (work.Block.NumberU64() % work.config.Clique.Epoch) == (work.config.Clique.Epoch - core.M1Gap) {
core.M1Ch <- 1
}
} }
self.push(work) self.push(work)
} }