|
package consensus
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"path"
|
|
"testing"
|
|
|
|
"github.com/tendermint/tendermint/config/tendermint_test"
|
|
// . "github.com/tendermint/go-common"
|
|
"github.com/tendermint/abci/example/dummy"
|
|
cfg "github.com/tendermint/go-config"
|
|
"github.com/tendermint/go-crypto"
|
|
dbm "github.com/tendermint/go-db"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
sm "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
var (
|
|
privKey = crypto.GenPrivKeyEd25519FromSecret([]byte("handshake_test"))
|
|
chainID = "handshake_chain"
|
|
nBlocks = 5
|
|
mempool = sm.MockMempool{}
|
|
testPartSize = 65536
|
|
)
|
|
|
|
//---------------------------------------
|
|
// Test handshake/replay
|
|
|
|
// Sync from scratch
|
|
func _TestHandshakeReplayAll(t *testing.T) {
|
|
testHandshakeReplay(t, 0)
|
|
}
|
|
|
|
// Sync many, not from scratch
|
|
func _TestHandshakeReplaySome(t *testing.T) {
|
|
testHandshakeReplay(t, 1)
|
|
}
|
|
|
|
// Sync from lagging by one
|
|
func _TestHandshakeReplayOne(t *testing.T) {
|
|
testHandshakeReplay(t, nBlocks-1)
|
|
}
|
|
|
|
// Sync from caught up
|
|
func TestHandshakeReplayNone(t *testing.T) {
|
|
testHandshakeReplay(t, nBlocks)
|
|
}
|
|
|
|
// Make some blocks. Start a fresh app and apply n blocks. Then restart the app and sync it up with the remaining blocks
|
|
func testHandshakeReplay(t *testing.T, n int) {
|
|
config := tendermint_test.ResetConfig("proxy_test_")
|
|
config.Set("chain_id", chainID)
|
|
|
|
state, store := stateAndStore(config)
|
|
clientCreator := proxy.NewLocalClientCreator(dummy.NewPersistentDummyApplication(path.Join(config.GetString("db_dir"), "1")))
|
|
clientCreator2 := proxy.NewLocalClientCreator(dummy.NewPersistentDummyApplication(path.Join(config.GetString("db_dir"), "2")))
|
|
proxyApp := proxy.NewAppConns(config, clientCreator, sm.NewHandshaker(config, state, store, ReplayLastBlock))
|
|
if _, err := proxyApp.Start(); err != nil {
|
|
t.Fatalf("Error starting proxy app connections: %v", err)
|
|
}
|
|
chain, commits := makeBlockchain(t, proxyApp, state)
|
|
store.chain = chain //
|
|
store.commits = commits
|
|
latestAppHash := state.AppHash
|
|
proxyApp.Stop()
|
|
|
|
if n > 0 {
|
|
// start a new app without handshake, play n blocks
|
|
proxyApp = proxy.NewAppConns(config, clientCreator2, nil)
|
|
if _, err := proxyApp.Start(); err != nil {
|
|
t.Fatalf("Error starting proxy app connections: %v", err)
|
|
}
|
|
state2, _ := stateAndStore(config)
|
|
for i := 0; i < n; i++ {
|
|
block := chain[i]
|
|
err := state2.ApplyBlock(nil, proxyApp.Consensus(), block, block.MakePartSet(testPartSize).Header(), mempool)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
proxyApp.Stop()
|
|
}
|
|
|
|
// now start it with the handshake
|
|
handshaker := sm.NewHandshaker(config, state, store, ReplayLastBlock)
|
|
proxyApp = proxy.NewAppConns(config, clientCreator2, handshaker)
|
|
if _, err := proxyApp.Start(); err != nil {
|
|
t.Fatalf("Error starting proxy app connections: %v", err)
|
|
}
|
|
|
|
// get the latest app hash from the app
|
|
res, err := proxyApp.Query().InfoSync()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// the app hash should be synced up
|
|
if !bytes.Equal(latestAppHash, res.LastBlockAppHash) {
|
|
t.Fatalf("Expected app hashes to match after handshake/replay. got %X, expected %X", res.LastBlockAppHash, latestAppHash)
|
|
}
|
|
|
|
if handshaker.NBlocks() != nBlocks-n {
|
|
t.Fatalf("Expected handshake to sync %d blocks, got %d", nBlocks-n, handshaker.NBlocks())
|
|
}
|
|
|
|
}
|
|
|
|
//--------------------------
|
|
// utils for making blocks
|
|
|
|
// make some bogus txs
|
|
func txsFunc(blockNum int) (txs []types.Tx) {
|
|
for i := 0; i < 10; i++ {
|
|
txs = append(txs, types.Tx([]byte{byte(blockNum), byte(i)}))
|
|
}
|
|
return txs
|
|
}
|
|
|
|
// sign a commit vote
|
|
func signCommit(height, round int, hash []byte, header types.PartSetHeader) *types.Vote {
|
|
vote := &types.Vote{
|
|
ValidatorIndex: 0,
|
|
ValidatorAddress: privKey.PubKey().Address(),
|
|
Height: height,
|
|
Round: round,
|
|
Type: types.VoteTypePrecommit,
|
|
BlockID: types.BlockID{hash, header},
|
|
}
|
|
|
|
sig := privKey.Sign(types.SignBytes(chainID, vote))
|
|
vote.Signature = sig
|
|
return vote
|
|
}
|
|
|
|
// make a blockchain with one validator
|
|
func makeBlockchain(t *testing.T, proxyApp proxy.AppConns, state *sm.State) (blockchain []*types.Block, commits []*types.Commit) {
|
|
|
|
prevHash := state.LastBlockID.Hash
|
|
lastCommit := new(types.Commit)
|
|
prevParts := types.PartSetHeader{}
|
|
valHash := state.Validators.Hash()
|
|
prevBlockID := types.BlockID{prevHash, prevParts}
|
|
|
|
for i := 1; i < nBlocks+1; i++ {
|
|
block, parts := types.MakeBlock(i, chainID, txsFunc(i), lastCommit,
|
|
prevBlockID, valHash, state.AppHash, testPartSize)
|
|
fmt.Println(i)
|
|
fmt.Println(block.LastBlockID)
|
|
err := state.ApplyBlock(nil, proxyApp.Consensus(), block, block.MakePartSet(testPartSize).Header(), mempool)
|
|
if err != nil {
|
|
t.Fatal(i, err)
|
|
}
|
|
|
|
voteSet := types.NewVoteSet(chainID, i, 0, types.VoteTypePrecommit, state.Validators)
|
|
vote := signCommit(i, 0, block.Hash(), parts.Header())
|
|
_, err = voteSet.AddVote(vote)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
prevHash = block.Hash()
|
|
prevParts = parts.Header()
|
|
lastCommit = voteSet.MakeCommit()
|
|
prevBlockID = types.BlockID{prevHash, prevParts}
|
|
|
|
blockchain = append(blockchain, block)
|
|
commits = append(commits, lastCommit)
|
|
}
|
|
return blockchain, commits
|
|
}
|
|
|
|
// fresh state and mock store
|
|
func stateAndStore(config cfg.Config) (*sm.State, *mockBlockStore) {
|
|
stateDB := dbm.NewMemDB()
|
|
return sm.MakeGenesisState(stateDB, &types.GenesisDoc{
|
|
ChainID: chainID,
|
|
Validators: []types.GenesisValidator{
|
|
types.GenesisValidator{privKey.PubKey(), 10000, "test"},
|
|
},
|
|
AppHash: nil,
|
|
}), NewMockBlockStore(config)
|
|
}
|
|
|
|
//----------------------------------
|
|
// mock block store
|
|
|
|
type mockBlockStore struct {
|
|
config cfg.Config
|
|
chain []*types.Block
|
|
commits []*types.Commit
|
|
}
|
|
|
|
func NewMockBlockStore(config cfg.Config) *mockBlockStore {
|
|
return &mockBlockStore{config, nil, nil}
|
|
}
|
|
|
|
func (bs *mockBlockStore) Height() int { return len(bs.chain) }
|
|
func (bs *mockBlockStore) LoadBlock(height int) *types.Block { return bs.chain[height-1] }
|
|
func (bs *mockBlockStore) LoadBlockMeta(height int) *types.BlockMeta {
|
|
block := bs.chain[height-1]
|
|
return &types.BlockMeta{
|
|
BlockID: types.BlockID{block.Hash(), block.MakePartSet(bs.config.GetInt("block_part_size")).Header()},
|
|
Header: block.Header,
|
|
}
|
|
}
|
|
func (bs *mockBlockStore) LoadBlockPart(height int, index int) *types.Part { return nil }
|
|
func (bs *mockBlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
|
|
}
|
|
func (bs *mockBlockStore) LoadBlockCommit(height int) *types.Commit {
|
|
return bs.commits[height-1]
|
|
}
|
|
func (bs *mockBlockStore) LoadSeenCommit(height int) *types.Commit {
|
|
return bs.commits[height-1]
|
|
}
|