diff --git a/client/cmd/kcoin/main.go b/client/cmd/kcoin/main.go index 3fca5df59..0cde134ee 100644 --- a/client/cmd/kcoin/main.go +++ b/client/cmd/kcoin/main.go @@ -25,7 +25,6 @@ import ( "github.com/kowala-tech/kcoin/client/node" "gopkg.in/urfave/cli.v1" "github.com/kowala-tech/kcoin/client/version" - "github.com/blang/semver" "github.com/kowala-tech/kcoin/client/params" ) @@ -359,7 +358,7 @@ func mustBeLatestMajorVersion(ctx *cli.Context) { return } - current, err := semver.Make(params.Version) + current, err := version.MakeSemver(params.Version) if err != nil { log.Error("Error parsing current version, exiting checker", "err", err) return diff --git a/client/common/tx/confirmation.go b/client/common/tx/confirmation.go index b3c5d1c89..c50bfa7e7 100644 --- a/client/common/tx/confirmation.go +++ b/client/common/tx/confirmation.go @@ -13,15 +13,22 @@ type Backend interface { TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) } +// WaitMinedWithTimeout waits for tx to be mined on the blockchain within a given period of time. +func WaitMinedWithTimeout(backend Backend, txHash common.Hash, duration time.Duration) (*types.Receipt, error) { + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + return WaitMined(ctx, backend, txHash) +} + // WaitMined waits for tx to be mined on the blockchain. // It stops waiting when the context is canceled. -func WaitMined(ctx context.Context, b Backend, txHash common.Hash) (*types.Receipt, error) { +func WaitMined(ctx context.Context, backend Backend, txHash common.Hash) (*types.Receipt, error) { queryTicker := time.NewTicker(time.Second) defer queryTicker.Stop() logger := log.New("hash", txHash) for { - receipt, err := b.TransactionReceipt(ctx, txHash) + receipt, err := backend.TransactionReceipt(ctx, txHash) if receipt != nil { return receipt, nil } diff --git a/client/contracts/bindings/oracle/oracle.go b/client/contracts/bindings/oracle/oracle.go index 73da6d45f..cd697fc9e 100644 --- a/client/contracts/bindings/oracle/oracle.go +++ b/client/contracts/bindings/oracle/oracle.go @@ -6,6 +6,7 @@ import ( "github.com/kowala-tech/kcoin/client/accounts/abi/bind" "github.com/kowala-tech/kcoin/client/common/kns" "github.com/kowala-tech/kcoin/client/contracts/bindings" + "github.com/kowala-tech/kcoin/client/log" "github.com/kowala-tech/kcoin/client/params" ) @@ -30,6 +31,7 @@ func Bind(contractBackend bind.ContractBackend, chainID *big.Int) (bindings.Bind contractBackend, ) if err != nil { + log.Error("can't find Oracle for given Network", "chainID", chainID.String()) return nil, bindings.ErrNoAddress } diff --git a/client/contracts/bindings/stability/contract.go b/client/contracts/bindings/stability/contract.go index f55c9196d..d0b779d36 100644 --- a/client/contracts/bindings/stability/contract.go +++ b/client/contracts/bindings/stability/contract.go @@ -3,10 +3,10 @@ package stability import ( "math/big" - "github.com/kowala-tech/kcoin/client/common/kns" - "github.com/kowala-tech/kcoin/client/accounts/abi/bind" + "github.com/kowala-tech/kcoin/client/common/kns" "github.com/kowala-tech/kcoin/client/contracts/bindings" + "github.com/kowala-tech/kcoin/client/log" "github.com/kowala-tech/kcoin/client/params" ) @@ -29,6 +29,7 @@ func Bind(contractBackend bind.ContractBackend, chainID *big.Int) (bindings.Bind contractBackend, ) if err != nil { + log.Error("can't find Stability contract for given Network", "chainID", chainID.String()) return nil, bindings.ErrNoAddress } diff --git a/client/contracts/bindings/sysvars/vars.go b/client/contracts/bindings/sysvars/vars.go index 2cce72153..ae1ab542b 100644 --- a/client/contracts/bindings/sysvars/vars.go +++ b/client/contracts/bindings/sysvars/vars.go @@ -3,10 +3,10 @@ package sysvars import ( "math/big" - "github.com/kowala-tech/kcoin/client/common/kns" - "github.com/kowala-tech/kcoin/client/accounts/abi/bind" + "github.com/kowala-tech/kcoin/client/common/kns" "github.com/kowala-tech/kcoin/client/contracts/bindings" + "github.com/kowala-tech/kcoin/client/log" "github.com/kowala-tech/kcoin/client/params" ) @@ -29,6 +29,7 @@ func Bind(contractBackend bind.ContractBackend, chainID *big.Int) (bindings.Bind contractBackend, ) if err != nil { + log.Error("can't find SystemVar for given Network", "chainID", chainID.String()) return nil, bindings.ErrNoAddress } diff --git a/client/core/blockchain.go b/client/core/blockchain.go index bd08b352b..81411aff4 100644 --- a/client/core/blockchain.go +++ b/client/core/blockchain.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "time" + "github.com/davecgh/go-spew/spew" "github.com/hashicorp/golang-lru" "github.com/kowala-tech/kcoin/client/common" "github.com/kowala-tech/kcoin/client/common/mclock" @@ -28,7 +29,6 @@ import ( "github.com/kowala-tech/kcoin/client/rlp" "github.com/kowala-tech/kcoin/client/trie" "gopkg.in/karalabe/cookiejar.v2/collections/prque" - "github.com/davecgh/go-spew/spew" ) var ( @@ -922,10 +922,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. func (bc *BlockChain) doReorg(block *types.Block, currentBlock *types.Block, batch kcoindb.Batch, state *state.StateDB) (WriteStatus, error) { // Reorganise the chain if the parent is not the head block - if IsHead(block, currentBlock) { - log.Warn(fmt.Sprintf("a blockchain reorganization needed: block parent hash %v, current block hash %v", - block.ParentHash().String(), currentBlock.Hash().String())) - + if !currentBlock.IsParent(block) { if err := bc.reorg(currentBlock, block); err != nil { return NonStatTy, err } @@ -940,10 +937,6 @@ func writePositionalMetadata(batch kcoindb.Batch, block *types.Block, state *sta rawdb.WritePreimages(batch, block.NumberU64(), state.Preimages()) } -func IsHead(block *types.Block, currentBlock *types.Block) bool { - return block.ParentHash() != currentBlock.Hash() -} - func (bc *BlockChain) isReorgState(block *types.Block, currentBlock *types.Block) bool { reorg := block.Number().Cmp(currentBlock.Number()) > 0 if !reorg && block.Number().Cmp(currentBlock.Number()) == 0 { @@ -1196,6 +1189,14 @@ func countTransactions(chain []*types.Block) (c int) { // to be part of the new canonical chain and accumulates potential missing transactions and post an // event about them func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { + if oldBlock.IsSame(newBlock) { + // don't need a reorg if got the same block as old and new ones + return nil + } + log.Warn(fmt.Sprintf("a blockchain reorganization needed: block parent hash %v(%d), current block hash %v(%d)", + newBlock.ParentHash().String(), newBlock.Number().Int64()-1, + oldBlock.Hash().String(), oldBlock.Number().Int64())) + var ( newChain types.Blocks oldChain types.Blocks @@ -1272,7 +1273,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { logFn("Chain split detected", "number", commonBlock.Number(), "hash", commonBlock.Hash(), "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash()) } else { - log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "len(oldChain)", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "len(newChain)", len(newChain)) + log.Error("Impossible reorg, please file an issue", + "oldnum", oldBlock.Number(), + "oldhash", oldBlock.Hash(), + "len(oldChain)", len(oldChain), + "newnum", newBlock.Number(), + "newhash", newBlock.Hash(), "len(newChain)", len(newChain)) } // Insert the new chain, taking care of the proper incremental order var addedTxs types.Transactions diff --git a/client/core/headerchain.go b/client/core/headerchain.go index ec8fd7f7f..8e1b0749b 100644 --- a/client/core/headerchain.go +++ b/client/core/headerchain.go @@ -133,12 +133,18 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er rawdb.DeleteCanonicalHash(batch, i) } batch.Write() + // Overwrite any stale canonical number assignments var ( headHash = header.ParentHash headNumber = header.Number.Uint64() - 1 headHeader = hc.GetHeader(headHash, headNumber) ) + if headHeader == nil { + log.Error("error processing block head", "hash", headHash, "number", headNumber) + log.Error("error processing block head. additional info", "headerByHash", hc.GetHeaderByHash(headHash)) + log.Error("error processing block head. additional info", "headerByNumber", hc.GetHeaderByNumber(headNumber)) + } for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { rawdb.WriteCanonicalHash(hc.chainDb, headHash, headNumber) @@ -175,7 +181,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) // Do a sanity check that the provided chain is actually ordered and linked for i := 1; i < len(chain); i++ { if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() { - // Chain broke ancestry, log a messge (programming error) and skip insertion + // Chain broke ancestry, log a message (programming error) and skip insertion log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash()) diff --git a/client/core/types/block.go b/client/core/types/block.go index 715319e93..a881ca8c5 100644 --- a/client/core/types/block.go +++ b/client/core/types/block.go @@ -2,6 +2,7 @@ package types import ( + "bytes" "io" "math/big" "sort" @@ -352,6 +353,16 @@ func (b *Block) AsFragments(size int) (*BlockFragments, error) { return NewDataSetFromData(rawBlock, size), nil } +// IsSame returns true if blocks have same hashes and block numbers +func (b *Block) IsSame(to *Block) bool { + return b.Number().Cmp(to.Number()) == 0 && bytes.Equal(b.Hash().Bytes(), to.Hash().Bytes()) +} + +// IsParent returns true if child block have parent hashes in ParentHash and block number greater by 1 +func (b *Block) IsParent(childBlock *Block) bool { + return b.Number().Int64()+1 == childBlock.Number().Int64() && bytes.Equal(b.Hash().Bytes(), childBlock.ParentHash().Bytes()) +} + type Blocks []*Block type BlockBy func(b1, b2 *Block) bool diff --git a/client/core/types/vote.go b/client/core/types/vote.go index 7538379c2..097ecbcba 100644 --- a/client/core/types/vote.go +++ b/client/core/types/vote.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "errors" "fmt" "io" "math/big" @@ -256,16 +257,28 @@ func (v *VotesSet) Add(vote *Vote) { } } -func (v *VotesSet) Contains(h common.Hash) bool { +var ( + errNonNilDuplicate = errors.New("duplicate NON-NIL vote") + errNilDuplicate = errors.New("duplicate NIL vote") +) + +func (v *VotesSet) Contains(h common.Hash) error { v.l.RLock() + defer v.l.RUnlock() + _, res := v.m[h] + if res { + return errNonNilDuplicate + } if !res { _, res = v.nilVotes[h] + if res { + return errNilDuplicate + } } - v.l.RUnlock() - return res + return nil } func (v *VotesSet) Len() int { diff --git a/client/core/voting_table.go b/client/core/voting_table.go index a62aa6a88..110d7a858 100644 --- a/client/core/voting_table.go +++ b/client/core/voting_table.go @@ -9,8 +9,6 @@ import ( "github.com/kowala-tech/kcoin/client/log" ) -var ErrDuplicateVote = errors.New("duplicate vote") - type VotingTable interface { Add(vote types.AddressVote) error Leader() common.Hash @@ -42,14 +40,11 @@ func (table *votingTable) Add(voteAddressed types.AddressVote) error { if !table.isVoter(voteAddressed.Address()) { return fmt.Errorf("voter address not found in voting table: 0x%x", voteAddressed.Address().Hash()) } - - vote := voteAddressed.Vote() - if table.isDuplicate(vote) { - log.Error(fmt.Sprintf("a duplicate vote in voting table %v; blockHash %v; voteHash %v. Error: %s", - table.voteType, vote.BlockHash(), vote.Hash(), vote.String())) - return ErrDuplicateVote + if err := table.isDuplicate(voteAddressed); err != nil { + return err } + vote := voteAddressed.Vote() table.votes.Add(vote) if table.hasQuorum() { @@ -64,8 +59,14 @@ func (table *votingTable) Leader() common.Hash { return table.votes.Leader() } -func (table *votingTable) isDuplicate(vote *types.Vote) bool { - return table.votes.Contains(vote.Hash()) +func (table *votingTable) isDuplicate(voteAddressed types.AddressVote) error { + vote := voteAddressed.Vote() + err := table.votes.Contains(vote.Hash()) + if err != nil { + log.Error(fmt.Sprintf("a duplicate vote in voting table %v; blockHash %v; voteHash %v; from validator %v. Error: %s", + table.voteType, vote.BlockHash(), vote.Hash(), voteAddressed.Address(), vote.String())) + } + return err } func (table *votingTable) isVoter(address common.Address) bool { diff --git a/client/knode/service.go b/client/knode/service.go index da55f74bd..4f6d95237 100644 --- a/client/knode/service.go +++ b/client/knode/service.go @@ -467,9 +467,12 @@ func (s *Kowala) Start(srvr *p2p.Server) error { } } + //fixme: should be removed after develop light client if srvr.DiscoveryV5 { - protocolTopic := discv5.DiscoveryTopic(s.blockchain.Genesis().Hash(), protocol.ProtocolName, protocol.Kcoin1) + chainID := s.chainConfig.ChainID.Int64() + networkID := s.networkID + protocolTopic := discv5.DiscoveryTopic(s.blockchain.Genesis().Hash(), protocol.ProtocolName, protocol.Kcoin1, networkID, chainID) go func() { srvr.DiscV5.RegisterTopic(protocolTopic, s.shutdownChan) diff --git a/client/knode/validator/states.go b/client/knode/validator/states.go index 6eefd97e1..a796c57c2 100644 --- a/client/knode/validator/states.go +++ b/client/knode/validator/states.go @@ -2,7 +2,6 @@ package validator import ( "bytes" - "context" "fmt" "math/big" "sync/atomic" @@ -58,7 +57,7 @@ func (val *validator) notLoggedInState() stateFn { } log.Info("Waiting confirmation to participate in the consensus") - receipt, err := tx.WaitMined(context.TODO(), val.backend, txHash) + receipt, err := tx.WaitMinedWithTimeout(val.backend, txHash, txConfirmationTimeout) if err != nil { log.Crit("Failed to verify the voter registration", "err", err) } diff --git a/client/knode/validator/validator.go b/client/knode/validator/validator.go index 8d476d2e6..6993047c7 100644 --- a/client/knode/validator/validator.go +++ b/client/knode/validator/validator.go @@ -34,6 +34,10 @@ var ( ErrIsRunning = errors.New("validator is running, cannot change its parameters") ) +var ( + txConfirmationTimeout = 10 * time.Second +) + // Backend wraps all methods required for mining. type Backend interface { BlockChain() *core.BlockChain @@ -416,7 +420,7 @@ func (val *validator) leave() { if err != nil { log.Error("failed to leave the election", "err", err) } - receipt, err := tx.WaitMined(context.TODO(), val.backend, txHash) + receipt, err := tx.WaitMinedWithTimeout(val.backend, txHash, txConfirmationTimeout) if err != nil { log.Error("Failed to verify the voter deregistration", "err", err) } @@ -531,7 +535,7 @@ func (val *validator) preVote() { log.Debug("Locked Block is not nil, voting for the locked block") vote = val.lockedBlock.Hash() case val.block == nil: - log.Debug("Proposal's block is nil, voting nil") + log.Warn("Proposal's block is nil, voting nil") vote = common.Hash{} default: log.Debug("Voting for the proposal's block") @@ -556,7 +560,7 @@ func (val *validator) preCommit() { // no majority // majority pre-voted nil case currentLeader == common.Hash{}: - log.Debug("Majority of validators pre-voted nil") + log.Warn("Majority of validators pre-voted nil") // unlock locked block if val.lockedBlock != nil { val.lockedRound = 0 @@ -579,7 +583,7 @@ func (val *validator) preCommit() { default: // fetch block, unlock, precommit // unlock locked block - log.Debug("preCommit default case") + log.Warn("preCommit default case") val.lockedRound = 0 val.lockedBlock = nil val.block = nil @@ -734,7 +738,7 @@ func (val *validator) RedeemDeposits() error { if err != nil { return err } - receipt, err := tx.WaitMined(context.TODO(), val.backend, txHash) + receipt, err := tx.WaitMinedWithTimeout(val.backend, txHash, txConfirmationTimeout) if err != nil { return err } diff --git a/client/p2p/discv5/topic.go b/client/p2p/discv5/topic.go index 0fb6ab683..acd7c7477 100644 --- a/client/p2p/discv5/topic.go +++ b/client/p2p/discv5/topic.go @@ -9,8 +9,8 @@ import ( "time" "github.com/kowala-tech/kcoin/client/common" - "github.com/kowala-tech/kcoin/client/log" "github.com/kowala-tech/kcoin/client/common/mclock" + "github.com/kowala-tech/kcoin/client/log" ) const ( @@ -392,7 +392,7 @@ func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64 heap.Fix(tq, item.index) } -func DiscoveryTopic(genesisHash common.Hash, protocolName string, protocolVersion uint) Topic { - protocolName = fmt.Sprintf("%s%d", strings.ToUpper(protocolName), protocolVersion) +func DiscoveryTopic(genesisHash common.Hash, protocolName string, protocolVersion uint, networkID uint64, chainID int64) Topic { + protocolName = fmt.Sprintf("%s%d-%d.%d", strings.ToUpper(protocolName), protocolVersion, networkID, chainID) return Topic(protocolName + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8])) } diff --git a/client/version/checker.go b/client/version/checker.go index f92aca7a6..1d64e524f 100644 --- a/client/version/checker.go +++ b/client/version/checker.go @@ -9,34 +9,34 @@ import ( "github.com/kowala-tech/kcoin/client/params" ) -const checkInterval = time.Minute - func Checker(repository string) { - current, err := semver.Make(params.Version) + current, err := MakeSemver(params.Version) if err != nil { log.Error("error parsing current version, exiting checker", "err", err) return } checker := &checker{ - repository: repository, - current: current, + repository: repository, + current: current, + checkInterval: time.Minute, } go checker.check() } type checker struct { - repository string - current semver.Version - latest semver.Version + repository string + current semver.Version + latest semver.Version + checkInterval time.Duration } func (c *checker) check() { - for range time.Tick(checkInterval) { + for { + time.Sleep(c.checkInterval) if c.isNewVersionAvailable() { c.printNewVersionAvailable() - // we exit this go routine, we only tell the user once - break + c.checkInterval = c.checkInterval * 2 } } } diff --git a/client/version/parser.go b/client/version/parser.go index d06df7f71..5594ec171 100644 --- a/client/version/parser.go +++ b/client/version/parser.go @@ -2,7 +2,6 @@ package version import ( "errors" - "github.com/blang/semver" "regexp" ) @@ -23,7 +22,7 @@ func filenameParser(filename string) (Asset, error) { return asset{}, errors.New("cant parse filename") } - version, err := semver.Make(matches[0][1]) + version, err := MakeSemver(matches[0][1]) if err != nil { return asset{}, err } diff --git a/client/version/repository.go b/client/version/repository.go index 3d285217b..e238d28ea 100644 --- a/client/version/repository.go +++ b/client/version/repository.go @@ -2,8 +2,10 @@ package version import ( "bufio" - "github.com/kowala-tech/kcoin/client/log" "net/http" + "time" + + "github.com/kowala-tech/kcoin/client/log" ) type AssetRepository interface { @@ -35,7 +37,8 @@ func (ar s3assetRepository) All() ([]Asset, error) { version, err := filenameParser(scanner.Text()) if err != nil { // ignore error and continue to next filename - log.Debug("could not parse filename", err) + log.Debug("could not parse filename", "err", err) + time.Sleep(500 * time.Millisecond) continue } assets = append(assets, version) diff --git a/client/version/semver_parser.go b/client/version/semver_parser.go new file mode 100644 index 000000000..c9959ff38 --- /dev/null +++ b/client/version/semver_parser.go @@ -0,0 +1,14 @@ +package version + +import ( + "github.com/blang/semver" + "strings" +) + +// MakeSemver returns a new semver version instance personalised to ignore Pre tag +func MakeSemver(s string) (semver.Version, error) { + if preIndex := strings.IndexRune(s, '-'); preIndex != -1 { + s = s[:preIndex] + } + return semver.Make(s) +} diff --git a/client/version/semver_parser_test.go b/client/version/semver_parser_test.go new file mode 100644 index 000000000..bf6473f80 --- /dev/null +++ b/client/version/semver_parser_test.go @@ -0,0 +1,26 @@ +package version + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMakeSemver(t *testing.T) { + semver, err := MakeSemver("2.0.0") + assert.NoError(t, err) + + assert.Equal(t, uint64(2), semver.Major) + assert.Equal(t, uint64(0), semver.Minor) + assert.Equal(t, uint64(0), semver.Patch) + assert.Len(t, semver.Pre, 0) +} + +func TestMakeSemverIgnoresPre(t *testing.T) { + semver, err := MakeSemver("2.0.1-stable") + assert.NoError(t, err) + + assert.Equal(t, uint64(2), semver.Major) + assert.Equal(t, uint64(0), semver.Minor) + assert.Equal(t, uint64(1), semver.Patch) + assert.Len(t, semver.Pre, 0) +} diff --git a/client/version/updater.go b/client/version/updater.go index cea106804..ec62add14 100644 --- a/client/version/updater.go +++ b/client/version/updater.go @@ -25,7 +25,7 @@ type updater struct { } func NewUpdater(repository string, logger log.Logger) (*updater, error) { - current, err := semver.Make(params.Version) + current, err := MakeSemver(params.Version) if err != nil { return nil, err }