diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 2ec0e90df4ff4d5df937f237ca4b3f5aa83b749f..cca20386f7725b2676e8096f145ff48c01d3276b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -92,98 +92,98 @@ }, { "ImportPath": "github.com/tendermint/tendermint/account", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/alert", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/blockchain", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/common", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/config", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/consensus", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/db", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/events", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/logger", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/mempool", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/merkle", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/node", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/p2p", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/permission/types", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/rpc", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/state", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/types", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/vm", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tendermint/tendermint/wire", - "Comment": "0.1-26-g85d5b16", - "Rev": "85d5b16dbc610a6a27f6c3d4ece0316907babec6" + "Comment": "0.1-61-g64bf8ec", + "Rev": "64bf8ec7a5c48327d4d0d12e74582806ffd1906b" }, { "ImportPath": "github.com/tommy351/gin-cors", diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/pool.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/pool.go index 66be4dc820cd983b702ddd6349473fd8a5192c76..b781dd3dc0973a15ad850157c770506a6f9ee58b 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/pool.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/pool.go @@ -12,8 +12,8 @@ import ( const ( requestIntervalMS = 250 - maxTotalRequests = 300 - maxPendingRequests = maxTotalRequests + maxTotalRequesters = 300 + maxPendingRequests = maxTotalRequesters maxPendingRequestsPerPeer = 75 peerTimeoutSeconds = 15 minRecvRate = 10240 // 10Kb/s @@ -36,8 +36,8 @@ type BlockPool struct { // block requests mtx sync.Mutex - requests map[int]*bpRequester - height int // the lowest key in requests. + requesters map[int]*bpRequester + height int // the lowest key in requesters. numPending int32 // number of requests pending assignment or block response // peers @@ -52,7 +52,7 @@ func NewBlockPool(start int, requestsCh chan<- BlockRequest, timeoutsCh chan<- s bp := &BlockPool{ peers: make(map[string]*bpPeer), - requests: make(map[int]*bpRequester), + requesters: make(map[int]*bpRequester), height: start, numPending: 0, @@ -65,7 +65,7 @@ func NewBlockPool(start int, requestsCh chan<- BlockRequest, timeoutsCh chan<- s func (pool *BlockPool) OnStart() error { pool.QuitService.OnStart() - go pool.makeRequestsRoutine() + go pool.makeRequestersRoutine() pool.startTime = time.Now() return nil } @@ -74,8 +74,8 @@ func (pool *BlockPool) OnStop() { pool.QuitService.OnStop() } -// Run spawns requests as needed. -func (pool *BlockPool) makeRequestsRoutine() { +// Run spawns requesters as needed. +func (pool *BlockPool) makeRequestersRoutine() { for { if !pool.IsRunning() { break @@ -86,14 +86,14 @@ func (pool *BlockPool) makeRequestsRoutine() { time.Sleep(requestIntervalMS * time.Millisecond) // check for timed out peers pool.removeTimedoutPeers() - } else if len(pool.requests) >= maxTotalRequests { + } else if len(pool.requesters) >= maxTotalRequesters { // sleep for a bit. time.Sleep(requestIntervalMS * time.Millisecond) // check for timed out peers pool.removeTimedoutPeers() } else { // request for more blocks. - pool.makeNextRequest() + pool.makeNextRequester() } } } @@ -147,10 +147,10 @@ func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) pool.mtx.Lock() // Lock defer pool.mtx.Unlock() - if r := pool.requests[pool.height]; r != nil { + if r := pool.requesters[pool.height]; r != nil { first = r.getBlock() } - if r := pool.requests[pool.height+1]; r != nil { + if r := pool.requesters[pool.height+1]; r != nil { second = r.getBlock() } return @@ -162,14 +162,18 @@ func (pool *BlockPool) PopRequest() { pool.mtx.Lock() // Lock defer pool.mtx.Unlock() - /* The block can disappear at any time, due to removePeer(). - if r := pool.requests[pool.height]; r == nil || r.block == nil { - PanicSanity("PopRequest() requires a valid block") + if r := pool.requesters[pool.height]; r != nil { + /* The block can disappear at any time, due to removePeer(). + if r := pool.requesters[pool.height]; r == nil || r.block == nil { + PanicSanity("PopRequest() requires a valid block") + } + */ + r.Stop() + delete(pool.requesters, pool.height) + pool.height++ + } else { + PanicSanity(Fmt("Expected requester to pop, got nothing at height %v", pool.height)) } - */ - - delete(pool.requests, pool.height) - pool.height++ } // Invalidates the block at pool.height, @@ -178,11 +182,11 @@ func (pool *BlockPool) RedoRequest(height int) { pool.mtx.Lock() // Lock defer pool.mtx.Unlock() - request := pool.requests[height] + request := pool.requesters[height] if request.block == nil { PanicSanity("Expected block to be non-nil") } - // RemovePeer will redo all requests associated with this peer. + // RemovePeer will redo all requesters associated with this peer. // TODO: record this malfeasance pool.RemovePeer(request.peerID) // Lock on peersMtx. } @@ -192,12 +196,12 @@ func (pool *BlockPool) AddBlock(peerID string, block *types.Block, blockSize int pool.mtx.Lock() // Lock defer pool.mtx.Unlock() - request := pool.requests[block.Height] - if request == nil { + requester := pool.requesters[block.Height] + if requester == nil { return } - if request.setBlock(block, peerID) { + if requester.setBlock(block, peerID) { pool.numPending-- peer := pool.getPeer(peerID) peer.decrPending(blockSize) @@ -228,10 +232,10 @@ func (pool *BlockPool) RemovePeer(peerID string) { } func (pool *BlockPool) removePeer(peerID string) { - for _, request := range pool.requests { - if request.getPeerID() == peerID { + for _, requester := range pool.requesters { + if requester.getPeerID() == peerID { pool.numPending++ - go request.redo() // pick another peer and ... + go requester.redo() // pick another peer and ... } } delete(pool.peers, peerID) @@ -269,14 +273,14 @@ func (pool *BlockPool) pickIncrAvailablePeer(minHeight int) *bpPeer { return nil } -func (pool *BlockPool) makeNextRequest() { +func (pool *BlockPool) makeNextRequester() { pool.mtx.Lock() // Lock defer pool.mtx.Unlock() - nextHeight := pool.height + len(pool.requests) + nextHeight := pool.height + len(pool.requesters) request := newBPRequester(pool, nextHeight) - pool.requests[nextHeight] = request + pool.requesters[nextHeight] = request pool.numPending++ request.Start() @@ -301,12 +305,12 @@ func (pool *BlockPool) debug() string { defer pool.mtx.Unlock() str := "" - for h := pool.height; h < pool.height+len(pool.requests); h++ { - if pool.requests[h] == nil { + for h := pool.height; h < pool.height+len(pool.requesters); h++ { + if pool.requesters[h] == nil { str += Fmt("H(%v):X ", h) } else { str += Fmt("H(%v):", h) - str += Fmt("B?(%v) ", pool.requests[h].block != nil) + str += Fmt("B?(%v) ", pool.requesters[h].block != nil) } } return str diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/reactor.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/reactor.go index bd50d5a679588550e537f29b1f174fe6089831db..1cab4ce61093eefd3fecc1631d45fe7f255071e3 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/reactor.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain/reactor.go @@ -192,7 +192,7 @@ FOR_LOOP: case _ = <-switchToConsensusTicker.C: height, numPending := bcR.pool.GetStatus() outbound, inbound, _ := bcR.Switch.NumPeers() - log.Info("Consensus ticker", "numPending", numPending, "total", len(bcR.pool.requests), + log.Info("Consensus ticker", "numPending", numPending, "total", len(bcR.pool.requesters), "outbound", outbound, "inbound", inbound) if bcR.pool.IsCaughtUp() { log.Notice("Time to switch to consensus reactor!", "height", height) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/common/io.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/common/io.go index 0c33b07569c3977bab84e037a7c567a759ebd923..378c19fc6a7361e9ba49877f2e2df471db19a40c 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/common/io.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/common/io.go @@ -1,6 +1,8 @@ package common import ( + "bytes" + "errors" "io" ) @@ -22,3 +24,52 @@ func (pr *PrefixedReader) Read(p []byte) (n int, err error) { return pr.reader.Read(p) } } + +// NOTE: Not goroutine safe +type BufferCloser struct { + bytes.Buffer + Closed bool +} + +func NewBufferCloser(buf []byte) *BufferCloser { + return &BufferCloser{ + *bytes.NewBuffer(buf), + false, + } +} + +func (bc *BufferCloser) Close() error { + if bc.Closed { + return errors.New("BufferCloser already closed") + } + bc.Closed = true + return nil +} + +func (bc *BufferCloser) Write(p []byte) (n int, err error) { + if bc.Closed { + return 0, errors.New("Cannot write to closed BufferCloser") + } + return bc.Buffer.Write(p) +} + +func (bc *BufferCloser) WriteByte(c byte) error { + if bc.Closed { + return errors.New("Cannot write to closed BufferCloser") + } + return bc.Buffer.WriteByte(c) +} + +func (bc *BufferCloser) WriteRune(r rune) (n int, err error) { + if bc.Closed { + return 0, errors.New("Cannot write to closed BufferCloser") + } + return bc.Buffer.WriteRune(r) +} + +func (bc *BufferCloser) WriteString(s string) (n int, err error) { + if bc.Closed { + return 0, errors.New("Cannot write to closed BufferCloser") + } + return bc.Buffer.WriteString(s) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/config.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/config.go index 43963e25ad7f5aa302db7df3e3d26dab5e160850..43bdf09441e6e0fd8d65dbc8a870126acce38a92 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/config.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/config.go @@ -1,8 +1,11 @@ package config import ( + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/naoina/toml" "sync" "time" + + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" ) type Config interface { @@ -19,28 +22,66 @@ type Config interface { Set(key string, value interface{}) } -type MapConfig map[string]interface{} +type MapConfig struct { + required map[string]struct{} // blows up if trying to use before setting. + data map[string]interface{} +} + +func ReadMapConfigFromFile(filePath string) (MapConfig, error) { + var configData = make(map[string]interface{}) + fileBytes := MustReadFile(filePath) + err := toml.Unmarshal(fileBytes, configData) + if err != nil { + return MapConfig{}, err + } + return NewMapConfig(configData), nil +} + +func NewMapConfig(data map[string]interface{}) MapConfig { + if data == nil { + data = make(map[string]interface{}) + } + return MapConfig{ + required: make(map[string]struct{}), + data: data, + } +} -func (cfg MapConfig) Get(key string) interface{} { return cfg[key] } -func (cfg MapConfig) GetBool(key string) bool { return cfg[key].(bool) } -func (cfg MapConfig) GetFloat64(key string) float64 { return cfg[key].(float64) } -func (cfg MapConfig) GetInt(key string) int { return cfg[key].(int) } -func (cfg MapConfig) GetString(key string) string { return cfg[key].(string) } +func (cfg MapConfig) Get(key string) interface{} { + if _, ok := cfg.required[key]; ok { + PanicSanity(Fmt("config key %v is required but was not set.", key)) + } + return cfg.data[key] +} +func (cfg MapConfig) GetBool(key string) bool { return cfg.Get(key).(bool) } +func (cfg MapConfig) GetFloat64(key string) float64 { return cfg.Get(key).(float64) } +func (cfg MapConfig) GetInt(key string) int { return cfg.Get(key).(int) } +func (cfg MapConfig) GetString(key string) string { return cfg.Get(key).(string) } func (cfg MapConfig) GetStringMap(key string) map[string]interface{} { - return cfg[key].(map[string]interface{}) + return cfg.Get(key).(map[string]interface{}) } func (cfg MapConfig) GetStringMapString(key string) map[string]string { - return cfg[key].(map[string]string) + return cfg.Get(key).(map[string]string) +} +func (cfg MapConfig) GetStringSlice(key string) []string { return cfg.Get(key).([]string) } +func (cfg MapConfig) GetTime(key string) time.Time { return cfg.Get(key).(time.Time) } +func (cfg MapConfig) IsSet(key string) bool { _, ok := cfg.data[key]; return ok } +func (cfg MapConfig) Set(key string, value interface{}) { + delete(cfg.required, key) + cfg.data[key] = value } -func (cfg MapConfig) GetStringSlice(key string) []string { return cfg[key].([]string) } -func (cfg MapConfig) GetTime(key string) time.Time { return cfg[key].(time.Time) } -func (cfg MapConfig) IsSet(key string) bool { _, ok := cfg[key]; return ok } -func (cfg MapConfig) Set(key string, value interface{}) { cfg[key] = value } func (cfg MapConfig) SetDefault(key string, value interface{}) { + delete(cfg.required, key) + if cfg.IsSet(key) { + return + } + cfg.data[key] = value +} +func (cfg MapConfig) SetRequired(key string) { if cfg.IsSet(key) { return } - cfg[key] = value + cfg.required[key] = struct{}{} } //-------------------------------------------------------------------------------- diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/config.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/config.go index a69e321ae96fccdd5c1e85b894bba43fca4c5f16..ae794c785f7570d7145c21b1ac64189cdc7e4592 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/config.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/config.go @@ -1,7 +1,6 @@ package tendermint import ( - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/naoina/toml" "os" "path" "strings" @@ -25,7 +24,6 @@ func initTMRoot(rootDir string) { EnsureDir(rootDir) configFilePath := path.Join(rootDir, "config.toml") - genesisFilePath := path.Join(rootDir, "genesis.json") // Write default config file if missing. if !FileExists(configFilePath) { @@ -33,19 +31,14 @@ func initTMRoot(rootDir string) { // moniker := cfg.Prompt("Type hostname: ", "anonymous") MustWriteFile(configFilePath, []byte(defaultConfig("anonymous"))) } - if !FileExists(genesisFilePath) { - MustWriteFile(genesisFilePath, []byte(defaultGenesis)) - } } func GetConfig(rootDir string) cfg.Config { rootDir = getTMRoot(rootDir) initTMRoot(rootDir) - var mapConfig = cfg.MapConfig(make(map[string]interface{})) configFilePath := path.Join(rootDir, "config.toml") - configFileBytes := MustReadFile(configFilePath) - err := toml.Unmarshal(configFileBytes, mapConfig) + mapConfig, err := cfg.ReadMapConfigFromFile(configFilePath) if err != nil { Exit(Fmt("Could not read config: %v", err)) } @@ -54,7 +47,10 @@ func GetConfig(rootDir string) cfg.Config { if mapConfig.IsSet("chain_id") { Exit("Cannot set 'chain_id' via config.toml") } - mapConfig.SetDefault("chain_id", "tendermint_testnet_10") + if mapConfig.IsSet("revision_file") { + Exit("Cannot set 'revision_file' via config.toml. It must match what's in the Makefile") + } + mapConfig.SetRequired("chain_id") // blows up if you try to use it before setting. mapConfig.SetDefault("genesis_file", rootDir+"/genesis.json") mapConfig.SetDefault("moniker", "anonymous") mapConfig.SetDefault("node_laddr", "0.0.0.0:46656") @@ -65,24 +61,22 @@ func GetConfig(rootDir string) cfg.Config { mapConfig.SetDefault("priv_validator_file", rootDir+"/priv_validator.json") mapConfig.SetDefault("db_backend", "leveldb") mapConfig.SetDefault("db_dir", rootDir+"/data") + mapConfig.SetDefault("vm_log", true) mapConfig.SetDefault("log_level", "info") mapConfig.SetDefault("rpc_laddr", "0.0.0.0:46657") - mapConfig.SetDefault("revisions_file", rootDir+"/revisions") + mapConfig.SetDefault("prof_laddr", "") + mapConfig.SetDefault("revision_file", rootDir+"/revision") + mapConfig.SetDefault("local_routing", false) + mapConfig.SetDefault("signer", "default") return mapConfig } -func ensureDefault(mapConfig cfg.MapConfig, key string, value interface{}) { - if !mapConfig.IsSet(key) { - mapConfig[key] = value - } -} - var defaultConfigTmpl = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml moniker = "__MONIKER__" node_laddr = "0.0.0.0:46656" -seeds = "goldenalchemist.chaintest.net:46656" +seeds = "" fast_sync = true db_backend = "leveldb" log_level = "notice" @@ -93,63 +87,3 @@ func defaultConfig(moniker string) (defaultConfig string) { defaultConfig = strings.Replace(defaultConfigTmpl, "__MONIKER__", moniker, -1) return } - -var defaultGenesis = `{ - "chain_id": "tendermint_testnet_11", - "accounts": [ - { - "address": "9FCBA7F840A0BFEBBE755E853C9947270A912D04", - "amount": 1995999998000000 - }, - { - "address": "964B1493BBE3312278B7DEB94C39149F7899A345", - "amount": 100000000000000 - }, - { - "address": "B9FA4AB462B9C6BF6A62DB4AE77C9E7087209A04", - "amount": 1000000000000 - }, - { - "address": "F171824590D69386F709E7B6704B369C5A370D60", - "amount": 1000000000000 - }, - { - "address": "56EFE746A13D9A6054AC89C3E2A361C2DB8B9EAE", - "amount": 1000000000000 - }, - { - "address": "7C2E032D8407EDF66A04D88CF0E1D9B15D98AE2D", - "amount": 1000000000000 - }, - { - "address": "A88A61069B6660F30F65E8786AFDD4F1D8F625E9", - "amount": 1000000 - }, - { - "address": "EE2EE9247973B4AFC3867CFE5F415410AC251B61", - "amount": 1000000 - } - ], - "validators": [ - { - "pub_key": [1, "178EC6008A4364508979C70CBF100BD4BCBAA12DDE6251F5F486B4FD09014F06"], - "amount": 100000000000 - }, - { - "pub_key": [1, "2A77777CC51467DE42350D4A8F34720D527734189BE64C7A930DD169E1FED3C6"], - "amount": 100000000000 - }, - { - "pub_key": [1, "3718E69D09B11B3AD3FA31AEF07EC416D2AEED241CACE7B0F30AE9803FFB0F08"], - "amount": 100000000000 - }, - { - "pub_key": [1, "C6B0440DEACD1E4CF1C736CEB8E38E788B700BA2B2045A55CB657A455CF5F889"], - "amount": 100000000000 - }, - { - "pub_key": [1, "3BA1190D54F91EFBF8B0125F7EC116AD4BA2894B6EE38564A5D5FD3230D91F7B"], - "amount": 100000000000 - } - ] -}` diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/genesis.json b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/genesis.json new file mode 100644 index 0000000000000000000000000000000000000000..eca00696edb4414df42fe6770cc23cde5a758529 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint/genesis.json @@ -0,0 +1,75 @@ +{ + "chain_id": "tendermint_testnet_11.c", + "accounts": [ + { + "address": "9FCBA7F840A0BFEBBE755E853C9947270A912D04", + "amount": 1991999998000000 + }, + { + "address": "964B1493BBE3312278B7DEB94C39149F7899A345", + "amount": 100000000000000 + }, + { + "address": "B9FA4AB462B9C6BF6A62DB4AE77C9E7087209A04", + "amount": 1000000000000 + }, + { + "address": "F171824590D69386F709E7B6704B369C5A370D60", + "amount": 1000000000000 + }, + { + "address": "56EFE746A13D9A6054AC89C3E2A361C2DB8B9EAE", + "amount": 1000000000000 + }, + { + "address": "7C2E032D8407EDF66A04D88CF0E1D9B15D98AE2D", + "amount": 1000000000000 + }, + { + "address": "636EF5823E082AD66EBC203FD4DFB1031F0C61CA", + "amount": 1000000000000 + }, + { + "address": "9008419E6351360A59B124E707E4CA2A5BFB9BE6", + "amount": 1000000000000 + }, + { + "address": "C78F48919B8A4030AD3E5ED643F8D2302E41953D", + "amount": 1000000000000 + }, + { + "address": "5290AC90CE2422DDC3F91F6A246F7E3C542EA51A", + "amount": 1000000000000 + }, + { + "address": "A88A61069B6660F30F65E8786AFDD4F1D8F625E9", + "amount": 1000000 + }, + { + "address": "EE2EE9247973B4AFC3867CFE5F415410AC251B61", + "amount": 1000000 + } + ], + "validators": [ + { + "pub_key": [1, "178EC6008A4364508979C70CBF100BD4BCBAA12DDE6251F5F486B4FD09014F06"], + "amount": 100000000000 + }, + { + "pub_key": [1, "2A77777CC51467DE42350D4A8F34720D527734189BE64C7A930DD169E1FED3C6"], + "amount": 100000000000 + }, + { + "pub_key": [1, "3718E69D09B11B3AD3FA31AEF07EC416D2AEED241CACE7B0F30AE9803FFB0F08"], + "amount": 100000000000 + }, + { + "pub_key": [1, "C6B0440DEACD1E4CF1C736CEB8E38E788B700BA2B2045A55CB657A455CF5F889"], + "amount": 100000000000 + }, + { + "pub_key": [1, "3BA1190D54F91EFBF8B0125F7EC116AD4BA2894B6EE38564A5D5FD3230D91F7B"], + "amount": 100000000000 + } + ] +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test/config.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test/config.go index 35a1b9b4362a6ce6d8cf1ad8b11f05dc0fc1b154..5347f988e852b580a6e393d3515ecc4548b95420 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test/config.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test/config.go @@ -3,7 +3,6 @@ package tendermint_test import ( - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/naoina/toml" "os" "path" "strings" @@ -47,10 +46,8 @@ func GetConfig(rootDir string) cfg.Config { rootDir = getTMRoot(rootDir) initTMRoot(rootDir) - var mapConfig = cfg.MapConfig(make(map[string]interface{})) configFilePath := path.Join(rootDir, "config.toml") - configFileBytes := MustReadFile(configFilePath) - err := toml.Unmarshal(configFileBytes, mapConfig) + mapConfig, err := cfg.ReadMapConfigFromFile(configFilePath) if err != nil { Exit(Fmt("Could not read config: %v", err)) } @@ -70,17 +67,15 @@ func GetConfig(rootDir string) cfg.Config { mapConfig.SetDefault("db_backend", "memdb") mapConfig.SetDefault("db_dir", rootDir+"/data") mapConfig.SetDefault("log_level", "debug") + mapConfig.SetDefault("vm_log", true) mapConfig.SetDefault("rpc_laddr", "0.0.0.0:36657") - mapConfig.SetDefault("revisions_file", rootDir+"/revisions") + mapConfig.SetDefault("prof_laddr", "") + mapConfig.SetDefault("revision_file", rootDir+"/revision") + mapConfig.SetDefault("local_routing", false) + mapConfig.SetDefault("signer", "default") return mapConfig } -func ensureDefault(mapConfig cfg.MapConfig, key string, value interface{}) { - if !mapConfig.IsSet(key) { - mapConfig[key] = value - } -} - var defaultConfigTmpl = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/reactor.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/reactor.go index 0b4378ee0fe4570c2a74a96d640df4d83a1a84ba..ad79bf52fa43f426c794b1b1810dd28e37e1dfba 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/reactor.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/reactor.go @@ -22,8 +22,6 @@ const ( DataChannel = byte(0x21) VoteChannel = byte(0x22) - PeerStateKey = "ConsensusReactor.peerState" - peerGossipSleepDuration = 100 * time.Millisecond // Time to sleep if there's nothing to send. ) @@ -107,7 +105,7 @@ func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) { // Create peerState for peer peerState := NewPeerState(peer) - peer.Data.Set(PeerStateKey, peerState) + peer.Data.Set(types.PeerStateKey, peerState) // Begin gossip routines for this peer. go conR.gossipDataRoutine(peer, peerState) @@ -138,7 +136,7 @@ func (conR *ConsensusReactor) Receive(chID byte, peer *p2p.Peer, msgBytes []byte } // Get peer states - ps := peer.Data.Get(PeerStateKey).(*PeerState) + ps := peer.Data.Get(types.PeerStateKey).(*PeerState) _, msg, err := DecodeMessage(msgBytes) if err != nil { log.Warn("Error decoding message", "channel", chID, "peer", peer, "msg", msg, "error", err, "bytes", msgBytes) @@ -455,35 +453,21 @@ OUTER_LOOP: } } // If there are prevotes to send... - if rs.Round == prs.Round && prs.Step <= RoundStepPrevote { - if ps.PickSendVote(rs.Votes.Prevotes(rs.Round)) { - log.Info("Picked rs.Prevotes(rs.Round) to send") - continue OUTER_LOOP - } - } - // If there are precommits to send... - if rs.Round == prs.Round && prs.Step <= RoundStepPrecommit { - if ps.PickSendVote(rs.Votes.Precommits(rs.Round)) { - log.Info("Picked rs.Precommits(rs.Round) to send") - continue OUTER_LOOP - } - } - // If there are prevotes to send for the last round... - if rs.Round == prs.Round+1 && prs.Step <= RoundStepPrevote { + if prs.Step <= RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { log.Info("Picked rs.Prevotes(prs.Round) to send") continue OUTER_LOOP } } - // If there are precommits to send for the last round... - if rs.Round == prs.Round+1 && prs.Step <= RoundStepPrecommit { + // If there are precommits to send... + if prs.Step <= RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) { log.Info("Picked rs.Precommits(prs.Round) to send") continue OUTER_LOOP } } // If there are POLPrevotes to send... - if 0 <= prs.ProposalPOLRound { + if prs.ProposalPOLRound != -1 { if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { if ps.PickSendVote(polPrevotes) { log.Info("Picked rs.Prevotes(prs.ProposalPOLRound) to send") @@ -548,8 +532,8 @@ type PeerRoundState struct { Precommits *BitArray // All precommits peer has for this round LastCommitRound int // Round of commit for last height. -1 if none. LastCommit *BitArray // All commit precommits of commit for last height. - CatchupCommitRound int // Round that we believe commit round is. -1 if none. - CatchupCommit *BitArray // All commit precommits peer has for this height + CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. + CatchupCommit *BitArray // All commit precommits peer has for this height & CatchupCommitRound } //----------------------------------------------------------------------------- @@ -588,6 +572,14 @@ func (ps *PeerState) GetRoundState() *PeerRoundState { return &prs } +// Returns an atomic snapshot of the PeerRoundState's height +// used by the mempool to ensure peers are caught up before broadcasting new txs +func (ps *PeerState) GetHeight() int { + ps.mtx.Lock() + defer ps.mtx.Unlock() + return ps.PeerRoundState.Height +} + func (ps *PeerState) SetHasProposal(proposal *types.Proposal) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -696,14 +688,18 @@ func (ps *PeerState) getVoteBitArray(height, round int, type_ byte) *BitArray { return nil } -// NOTE: 'round' is what we know to be the commit round for height. +// 'round': A round for which we have a +2/3 commit. func (ps *PeerState) ensureCatchupCommitRound(height, round int, numValidators int) { if ps.Height != height { return } - if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { - PanicSanity(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round)) - } + /* + NOTE: This is wrong, 'round' could change. + e.g. if orig round is not the same as block LastValidation round. + if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { + PanicSanity(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round)) + } + */ if ps.CatchupCommitRound == round { return // Nothing to do! } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state.go index 72ec17c6b4bb0df9a0e86900bc755e5f7d941f9c..2f8c5e746880374abe125e4901e65c24bf0b5b98 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state.go @@ -1308,14 +1308,12 @@ func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSe cs.stagedState.Save() // Update mempool. - cs.mempoolReactor.Mempool.ResetForBlockAndState(block, cs.stagedState) + cs.mempoolReactor.ResetForBlockAndState(block, cs.stagedState) // Fire off event if cs.evsw != nil && cs.evc != nil { - go func(block *types.Block) { - cs.evsw.FireEvent(types.EventStringNewBlock(), types.EventDataNewBlock{block}) - cs.evc.Flush() - }(block) + cs.evsw.FireEvent(types.EventStringNewBlock(), types.EventDataNewBlock{block}) + go cs.evc.Flush() } } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a3d8b3ae85065f2a3afd35c5cc69a3ddd87fcabc --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/state_test.go @@ -0,0 +1,1177 @@ +package consensus + +import ( + "bytes" + "fmt" + "testing" + "time" + + _ "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" +) + +/* + +ProposeSuite +x * TestEnterProposeNoValidator - timeout into prevote round +x * TestEnterPropose - finish propose without timing out (we have the proposal) +x * TestBadProposal - 2 vals, bad proposal (bad block state hash), should prevote and precommit nil +FullRoundSuite +x * TestFullRound1 - 1 val, full successful round +x * TestFullRoundNil - 1 val, full round of nil +x * TestFullRound2 - 2 vals, both required for fuill round +LockSuite +x * TestLockNoPOL - 2 vals, 4 rounds. one val locked, precommits nil every round except first. +x * TestLockPOLRelock - 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka +x * TestLockPOLUnlock - 4 vals, one precommits, other 3 polka nil at next round, so we unlock and precomit nil +x * TestLockPOLSafety1 - 4 vals. We shouldn't change lock based on polka at earlier round +x * TestLockPOLSafety2 - 4 vals. After unlocking, we shouldn't relock based on polka at earlier round + * TestNetworkLock - once +1/3 precommits, network should be locked + * TestNetworkLockPOL - once +1/3 precommits, the block with more recent polka is committed +SlashingSuite +x * TestSlashingPrevotes - a validator prevoting twice in a round gets slashed +x * TestSlashingPrecommits - a validator precomitting twice in a round gets slashed +CatchupSuite + * TestCatchup - if we might be behind and we've seen any 2/3 prevotes, round skip to new round, precommit, or prevote +HaltSuite +x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we should still commit + +*/ + +//---------------------------------------------------------------------------------------------------- +// ProposeSuite + +func init() { + fmt.Println("") + timeoutPropose = 1000 * time.Millisecond +} + +// a non-validator should timeout into the prevote round +func TestEnterProposeNoPrivValidator(t *testing.T) { + css, _ := simpleConsensusState(1) + cs := css[0] + cs.SetPrivValidator(nil) + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs.SetFireable(evsw) + + // starts a go routine for EnterPropose + cs.EnterNewRound(cs.Height, 0, false) + + // go to prevote + <-cs.NewStepCh() + + // if we're not a validator, EnterPropose should timeout + select { + case rs := <-cs.NewStepCh(): + log.Info(rs.String()) + t.Fatal("Expected EnterPropose to timeout") + case <-timeoutChan: + rs := cs.GetRoundState() + if rs.Proposal != nil { + t.Error("Expected to make no proposal, since no privValidator") + } + break + } +} + +// a validator should not timeout of the prevote round (TODO: unless the block is really big!) +func TestEnterPropose(t *testing.T) { + css, _ := simpleConsensusState(1) + cs := css[0] + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs.SetFireable(evsw) + + // starts a go routine for EnterPropose + cs.EnterNewRound(cs.Height, 0, false) + + // go to prevote + <-cs.NewStepCh() + + // if we are a validator, we expect it not to timeout + select { + case <-cs.NewStepCh(): + rs := cs.GetRoundState() + + // Check that Proposal, ProposalBlock, ProposalBlockParts are set. + if rs.Proposal == nil { + t.Error("rs.Proposal should be set") + } + if rs.ProposalBlock == nil { + t.Error("rs.ProposalBlock should be set") + } + if rs.ProposalBlockParts.Total() == 0 { + t.Error("rs.ProposalBlockParts should be set") + } + break + case <-timeoutChan: + t.Fatal("Expected EnterPropose not to timeout") + } +} + +func TestBadProposal(t *testing.T) { + css, privVals := simpleConsensusState(2) + cs1, cs2 := css[0], css[1] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs1.SetFireable(evsw) + + // make the second validator the proposer + propBlock := changeProposer(t, cs1, cs2) + + // make the block bad by tampering with statehash + stateHash := propBlock.StateHash + stateHash[0] = byte((stateHash[0] + 1) % 255) + propBlock.StateHash = stateHash + propBlockParts := propBlock.MakePartSet() + proposal := types.NewProposal(cs2.Height, cs2.Round, propBlockParts.Header(), cs2.Votes.POLRound()) + if err := cs2.privValidator.SignProposal(cs2.state.ChainID, proposal); err != nil { + t.Fatal("failed to sign bad proposal", err) + } + + // start round + cs1.EnterNewRound(cs1.Height, 0, false) + + // now we're on a new round and not the proposer + <-cs1.NewStepCh() + // so set the proposal block (and fix voting power) + cs1.mtx.Lock() + cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = proposal, propBlock, propBlockParts + fixVotingPower(t, cs1, privVals[1].Address) + cs1.mtx.Unlock() + // and wait for timeout + <-timeoutChan + + // go to prevote, prevote for nil (proposal is bad) + <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], nil) + + // add bad prevote from cs2. we should precommit nil + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) + _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() + validatePrecommit(t, cs1, 0, 0, privVals[0], nil, nil) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) +} + +//---------------------------------------------------------------------------------------------------- +// FulLRoundSuite + +// propose, prevote, and precommit a block +func TestFullRound1(t *testing.T) { + css, privVals := simpleConsensusState(1) + cs := css[0] + + // starts a go routine for EnterPropose + cs.EnterNewRound(cs.Height, 0, false) + // wait to finish propose and prevote + _, _ = <-cs.NewStepCh(), <-cs.NewStepCh() + + // we should now be in precommit + // verify our prevote is there + cs.mtx.Lock() + propBlockHash := cs.ProposalBlock.Hash() + cs.mtx.Unlock() + + // the proposed block should be prevoted, precommitted, and locked + validatePrevoteAndPrecommit(t, cs, 0, 0, privVals[0], propBlockHash, propBlockHash, nil) +} + +// nil is proposed, so prevote and precommit nil +func TestFullRoundNil(t *testing.T) { + css, privVals := simpleConsensusState(1) + cs := css[0] + cs.newStepCh = make(chan *RoundState) // so it blocks + cs.SetPrivValidator(nil) + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs.SetFireable(evsw) + + // starts a go routine for EnterPropose + cs.EnterNewRound(cs.Height, 0, false) + + // wait to finish propose (we should time out) + <-cs.NewStepCh() + cs.SetPrivValidator(privVals[0]) // this might be a race condition (uses the mutex that EnterPropose has just released and EnterPrevote is about to grab) + <-timeoutChan + + // wait to finish prevote + <-cs.NewStepCh() + + // should prevote and precommit nil + validatePrevoteAndPrecommit(t, cs, 0, 0, privVals[0], nil, nil, nil) +} + +// run through propose, prevote, precommit commit with two validators +// where the first validator has to wait for votes from the second +func TestFullRound2(t *testing.T) { + css, privVals := simpleConsensusState(2) + cs1, cs2 := css[0], css[1] + cs1.newStepCh = make(chan *RoundState) // so it blocks + cs2.newStepCh = make(chan *RoundState) // so it blocks + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + + // we should now be stuck in limbo forever, waiting for more prevotes + ensureNoNewStep(t, cs1) + + propBlockHash, propPartsHeader := cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header() + + // prevote arrives from cs2: + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlockHash, propPartsHeader) + + // wait to finish precommit + <-cs1.NewStepCh() + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], propBlockHash, propBlockHash) + + // we should now be stuck in limbo forever, waiting for more precommits + ensureNoNewStep(t, cs1) + + // precommit arrives from cs2: + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlockHash, propPartsHeader) + + // wait to finish commit, propose in next height + _, rs := <-cs1.NewStepCh(), <-cs1.NewStepCh() + if rs.Height != 2 { + t.Fatal("Expected height to increment") + } +} + +//------------------------------------------------------------------------------------------ +// LockSuite + +// two validators, 4 rounds. +// val1 proposes the first 2 rounds, and is locked in the first. +// val2 proposes the next two. val1 should precommit nil on all (except first where he locks) +func TestLockNoPOL(t *testing.T) { + css, privVals := simpleConsensusState(2) + cs1, cs2 := css[0], css[1] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs1.SetFireable(evsw) + + /* + Round1 (cs1, B) // B B // B B2 + */ + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + + // we should now be stuck in limbo forever, waiting for more prevotes + // prevote arrives from cs2: + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + + cs1.mtx.Lock() // XXX: sigh + theBlockHash := cs1.ProposalBlock.Hash() + cs1.mtx.Unlock() + + // wait to finish precommit + <-cs1.NewStepCh() + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) + + // we should now be stuck in limbo forever, waiting for more precommits + // lets add one for a different block + // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round + hash := cs1.ProposalBlock.Hash() + hash[0] = byte((hash[0] + 1) % 255) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + + // (note we're entering precommit for a second time this round) + // but with invalid args. then we EnterPrecommitWait, and the timeout to new round + _, _ = <-cs1.NewStepCh(), <-timeoutChan + + log.Info("#### ONTO ROUND 2") + /* + Round2 (cs1, B) // B B2 + */ + + incrementRound(cs2) + + // go to prevote + <-cs1.NewStepCh() + + // now we're on a new round and the proposer + if cs1.ProposalBlock != cs1.LockedBlock { + t.Fatalf("Expected proposal block to be locked block. Got %v, Expected %v", cs1.ProposalBlock, cs1.LockedBlock) + } + + // wait to finish prevote + <-cs1.NewStepCh() + + // we should have prevoted our locked block + validatePrevote(t, cs1, 1, privVals[0], cs1.LockedBlock.Hash()) + + // add a conflicting prevote from the other validator + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + + // now we're going to enter prevote again, but with invalid args + // and then prevote wait, which should timeout. then wait for precommit + _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() + + // the proposed block should still be locked and our precommit added + // we should precommit nil and be locked on the proposal + validatePrecommit(t, cs1, 1, 0, privVals[0], nil, theBlockHash) + + // add conflicting precommit from cs2 + // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + + // (note we're entering precommit for a second time this round, but with invalid args + // then we EnterPrecommitWait and timeout into NewRound + _, _ = <-cs1.NewStepCh(), <-timeoutChan + + log.Info("#### ONTO ROUND 3") + /* + Round3 (cs2, _) // B, B2 + */ + + incrementRound(cs2) + + // now we're on a new round and not the proposer, so wait for timeout + _, _ = <-cs1.NewStepCh(), <-timeoutChan + if cs1.ProposalBlock != nil { + t.Fatal("Expected proposal block to be nil") + } + + // go to prevote, prevote for locked block + <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) + + // TODO: quick fastforward to new round, set proposer + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() + validatePrecommit(t, cs1, 2, 0, privVals[0], nil, theBlockHash) // precommit nil but be locked on proposal + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // NOTE: conflicting precommits at same height + + <-cs1.NewStepCh() + + // before we time out into new round, set next proposer + // and next proposal block + _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + } + + cs2.decideProposal(cs2.Height, cs2.Round+1) + prop, propBlock := cs2.Proposal, cs2.ProposalBlock + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with cs2") + } + + incrementRound(cs2) + + <-timeoutChan + + log.Info("#### ONTO ROUND 4") + /* + Round4 (cs2, C) // B C // B C + */ + + // now we're on a new round and not the proposer + <-cs1.NewStepCh() + // so set the proposal block + cs1.mtx.Lock() + cs1.Proposal, cs1.ProposalBlock = prop, propBlock + cs1.mtx.Unlock() + // and wait for timeout + <-timeoutChan + // go to prevote, prevote for locked block (not proposal) + <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) + + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) + _, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh() + validatePrecommit(t, cs1, 2, 0, privVals[0], nil, theBlockHash) // precommit nil but locked on proposal + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) // NOTE: conflicting precommits at same height +} + +// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka +func TestLockPOLRelock(t *testing.T) { + css, privVals := simpleConsensusState(4) + cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan *types.EventDataRoundState) + voteChan := make(chan *types.EventDataVote) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { + vote := data.(*types.EventDataVote) + // we only fire for our own votes + if bytes.Equal(cs1.privValidator.Address, vote.Address) { + voteChan <- vote + } + }) + cs1.SetFireable(evsw) + + // everything done from perspective of cs1 + + /* + Round1 (cs1, B) // B B B B// B nil B nil + + eg. cs2 and cs4 didn't see the 2/3 prevotes + */ + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + + theBlockHash := cs1.ProposalBlock.Hash() + + // wait to finish precommit after prevotes done + // we do this in a go routine with another channel since otherwise + // we may get deadlock with EnterPrecommit waiting to send on newStepCh and the final + // signAddVoteToFrom waiting for the cs.mtx.Lock + donePrecommit := make(chan struct{}) + go func() { + <-voteChan + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) + <-donePrecommit + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) + + donePrecommitWait := make(chan struct{}) + go func() { + // (note we're entering precommit for a second time this round) + // but with invalid args. then we EnterPrecommitWait, twice (?) + <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + // add precommits from the rest + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + <-donePrecommitWait + + // before we time out into new round, set next proposer + // and next proposal block + _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + } + + cs2.decideProposal(cs2.Height, cs2.Round+1) + prop, propBlock := cs2.Proposal, cs2.ProposalBlock + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with cs2") + } + + incrementRound(cs2, cs3, cs4) + + // timeout to new round + te := <-timeoutChan + if te.Step != RoundStepPrecommitWait.String() { + t.Fatalf("expected to timeout of precommit into new round. got %v", te.Step) + } + + log.Info("### ONTO ROUND 2") + + /* + Round2 (cs2, C) // B C C C // C C C _) + + cs1 changes lock! + */ + + // now we're on a new round and not the proposer + <-cs1.NewStepCh() + cs1.mtx.Lock() + // so set the proposal block + propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() + cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts + cs1.mtx.Unlock() + // and wait for timeout + te = <-timeoutChan + if te.Step != RoundStepPropose.String() { + t.Fatalf("expected to timeout of propose. got %v", te.Step) + } + // go to prevote, prevote for locked block (not proposal), move on + _, _ = <-voteChan, <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], theBlockHash) + + donePrecommit = make(chan struct{}) + go func() { + // we need this go routine because if we go into PrevoteWait it has to pull on newStepCh + // before the final vote will get added (because it holds the mutex). + select { + case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit + <-voteChan + case <-voteChan: // we went straight to Precommit + } + donePrecommit <- struct{}{} + }() + // now lets add prevotes from everyone else for the new block + signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) + <-donePrecommit + + // we should have unlocked and locked on the new block + validatePrecommit(t, cs1, 1, 1, privVals[0], propBlockHash, propBlockHash) + + donePrecommitWait = make(chan struct{}) + go func() { + // (note we're entering precommit for a second time this round) + // but with invalid args. then we EnterPrecommitWait, + <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3) + <-donePrecommitWait + + <-cs1.NewStepCh() + rs := <-cs1.NewStepCh() + if rs.Height != 2 { + t.Fatal("Expected height to increment") + } + + if hash, _, ok := rs.LastCommit.TwoThirdsMajority(); !ok || !bytes.Equal(hash, propBlockHash) { + t.Fatal("Expected block to get committed") + } +} + +// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka +func TestLockPOLUnlock(t *testing.T) { + css, privVals := simpleConsensusState(4) + cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan *types.EventDataRoundState) + voteChan := make(chan *types.EventDataVote) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { + vote := data.(*types.EventDataVote) + // we only fire for our own votes + if bytes.Equal(cs1.privValidator.Address, vote.Address) { + voteChan <- vote + } + }) + cs1.SetFireable(evsw) + + // everything done from perspective of cs1 + + /* + Round1 (cs1, B) // B B B B // B nil B nil + + eg. didn't see the 2/3 prevotes + */ + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + + theBlockHash := cs1.ProposalBlock.Hash() + + donePrecommit := make(chan struct{}) + go func() { + <-voteChan + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) + <-donePrecommit + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) + + donePrecommitWait := make(chan struct{}) + go func() { + <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + // add precommits from the rest + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + <-donePrecommitWait + + // before we time out into new round, set next proposer + // and next proposal block + _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + } + + cs2.decideProposal(cs2.Height, cs2.Round+1) + prop, propBlock := cs2.Proposal, cs2.ProposalBlock + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with cs2") + } + + incrementRound(cs2, cs3, cs4) + + // timeout to new round + <-timeoutChan + + log.Info("#### ONTO ROUND 2") + /* + Round2 (cs2, C) // B nil nil nil // nil nil nil _ + + cs1 unlocks! + */ + + // now we're on a new round and not the proposer, + <-cs1.NewStepCh() + cs1.mtx.Lock() + // so set the proposal block + cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() + lockedBlockHash := cs1.LockedBlock.Hash() + cs1.mtx.Unlock() + // and wait for timeout + <-timeoutChan + + // go to prevote, prevote for locked block (not proposal) + _, _ = <-voteChan, <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], lockedBlockHash) + + donePrecommit = make(chan struct{}) + go func() { + select { + case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit + <-voteChan + case <-voteChan: // we went straight to Precommit + } + donePrecommit <- struct{}{} + }() + // now lets add prevotes from everyone else for the new block + signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) + <-donePrecommit + + // we should have unlocked + // NOTE: we don't lock on nil, so LockedRound is still 0 + validatePrecommit(t, cs1, 1, 0, privVals[0], nil, nil) + + donePrecommitWait = make(chan struct{}) + go func() { + // the votes will bring us to new round right away + // we should timeout of it + _, _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh(), <-timeoutChan + donePrecommitWait <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) + <-donePrecommitWait +} + +// 4 vals +// a polka at round 1 but we miss it +// then a polka at round 2 that we lock on +// then we see the polka from round 1 but shouldn't unlock +func TestLockPOLSafety1(t *testing.T) { + css, privVals := simpleConsensusState(4) + cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan *types.EventDataRoundState) + voteChan := make(chan *types.EventDataVote) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { + vote := data.(*types.EventDataVote) + // we only fire for our own votes + if bytes.Equal(cs1.privValidator.Address, vote.Address) { + voteChan <- vote + } + }) + cs1.SetFireable(evsw) + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + + propBlock := cs1.ProposalBlock + + validatePrevote(t, cs1, 0, privVals[0], cs1.ProposalBlock.Hash()) + + // the others sign a polka but we don't see it + prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) + + // before we time out into new round, set next proposer + // and next proposal block + _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + } + + log.Warn("old prop", "hash", fmt.Sprintf("%X", propBlock.Hash())) + + // we do see them precommit nil + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) + + cs2.decideProposal(cs2.Height, cs2.Round+1) + prop, propBlock := cs2.Proposal, cs2.ProposalBlock + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with cs2") + } + + incrementRound(cs2, cs3, cs4) + + log.Info("### ONTO ROUND 2") + /*Round2 + // we timeout and prevote our lock + // a polka happened but we didn't see it! + */ + + // now we're on a new round and not the proposer, + <-cs1.NewStepCh() + // so set proposal + cs1.mtx.Lock() + propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() + cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts + cs1.mtx.Unlock() + // and wait for timeout + <-timeoutChan + if cs1.LockedBlock != nil { + t.Fatal("we should not be locked!") + } + log.Warn("new prop", "hash", fmt.Sprintf("%X", propBlockHash)) + // go to prevote, prevote for proposal block + _, _ = <-voteChan, <-cs1.NewStepCh() + validatePrevote(t, cs1, 1, privVals[0], propBlockHash) + + // now we see the others prevote for it, so we should lock on it + donePrecommit := make(chan struct{}) + go func() { + select { + case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit + <-voteChan + case <-voteChan: // we went straight to Precommit + } + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + // now lets add prevotes from everyone else for nil + signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) + <-donePrecommit + + // we should have precommitted + validatePrecommit(t, cs1, 1, 1, privVals[0], propBlockHash, propBlockHash) + + // now we see precommits for nil + donePrecommitWait := make(chan struct{}) + go func() { + // the votes will bring us to new round + // we should timeut of it and go to prevote + <-cs1.NewStepCh() + <-timeoutChan + donePrecommitWait <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) + <-donePrecommitWait + + incrementRound(cs2, cs3, cs4) + + log.Info("### ONTO ROUND 3") + /*Round3 + we see the polka from round 1 but we shouldn't unlock! + */ + + // timeout of propose + _, _ = <-cs1.NewStepCh(), <-timeoutChan + + // finish prevote + _, _ = <-voteChan, <-cs1.NewStepCh() + + // we should prevote what we're locked on + validatePrevote(t, cs1, 2, privVals[0], propBlockHash) + + // add prevotes from the earlier round + addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) + + log.Warn("Done adding prevotes!") + + ensureNoNewStep(t, cs1) +} + +// 4 vals. +// polka P1 at R1, P2 at R2, and P3 at R3, +// we lock on P1 at R1, don't see P2, and unlock using P3 at R3 +// then we should make sure we don't lock using P2 +func TestLockPOLSafety2(t *testing.T) { + css, privVals := simpleConsensusState(4) + cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan *types.EventDataRoundState) + voteChan := make(chan *types.EventDataVote) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- data.(*types.EventDataRoundState) + }) + evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { + vote := data.(*types.EventDataVote) + // we only fire for our own votes + if bytes.Equal(cs1.privValidator.Address, vote.Address) { + voteChan <- vote + } + }) + cs1.SetFireable(evsw) + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + + theBlockHash := cs1.ProposalBlock.Hash() + + donePrecommit := make(chan struct{}) + go func() { + <-voteChan + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) + <-donePrecommit + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) + + donePrecommitWait := make(chan struct{}) + go func() { + <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + // add precommits from the rest + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + <-donePrecommitWait + + // before we time out into new round, set next proposer + // and next proposal block + _, v1 := cs1.Validators.GetByAddress(privVals[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + } + + cs2.decideProposal(cs2.Height, cs2.Round+1) + prop, propBlock := cs2.Proposal, cs2.ProposalBlock + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with cs2") + } + + incrementRound(cs2, cs3, cs4) + + // timeout to new round + <-timeoutChan + + log.Info("### ONTO Round 2") + /*Round2 + // we timeout and prevote our lock + // a polka happened but we didn't see it! + */ + + // now we're on a new round and not the proposer, so wait for timeout + _, _ = <-cs1.NewStepCh(), <-timeoutChan + // go to prevote, prevote for locked block + _, _ = <-voteChan, <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) + + // the others sign a polka but we don't see it + prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) + + // once we see prevotes for the next round we'll skip ahead + + incrementRound(cs2, cs3, cs4) + + log.Info("### ONTO Round 3") + /*Round3 + a polka for nil causes us to unlock + */ + + // these prevotes will send us straight to precommit at the higher round + donePrecommit = make(chan struct{}) + go func() { + select { + case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit + <-voteChan + case <-voteChan: // we went straight to Precommit + } + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + // now lets add prevotes from everyone else for nil + signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) + <-donePrecommit + + // we should have unlocked + // NOTE: we don't lock on nil, so LockedRound is still 0 + validatePrecommit(t, cs1, 2, 0, privVals[0], nil, nil) + + donePrecommitWait = make(chan struct{}) + go func() { + // the votes will bring us to new round right away + // we should timeut of it and go to prevote + <-cs1.NewStepCh() + // set the proposal block to be that which got a polka in R2 + cs1.mtx.Lock() + cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() + cs1.mtx.Unlock() + // timeout into prevote, finish prevote + _, _, _ = <-timeoutChan, <-voteChan, <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) + <-donePrecommitWait + + log.Info("### ONTO ROUND 4") + /*Round4 + we see the polka from R2 + make sure we don't lock because of it! + */ + // new round and not proposer + // (we already timed out and stepped into prevote) + + log.Warn("adding prevotes from round 2") + + addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) + + log.Warn("Done adding prevotes!") + + // we should prevote it now + validatePrevote(t, cs1, 3, privVals[0], cs1.ProposalBlock.Hash()) + + // but we shouldn't precommit it + precommits := cs1.Votes.Precommits(3) + vote := precommits.GetByIndex(0) + if vote != nil { + t.Fatal("validator precommitted at round 4 based on an old polka") + } +} + +//------------------------------------------------------------------------------------------ +// SlashingSuite + +func TestSlashingPrevotes(t *testing.T) { + css, _ := simpleConsensusState(2) + cs1, cs2 := css[0], css[1] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + + // we should now be stuck in limbo forever, waiting for more prevotes + // add one for a different block should cause us to go into prevote wait + hash := cs1.ProposalBlock.Hash() + hash[0] = byte(hash[0]+1) % 255 + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + + // pass prevote wait + <-cs1.NewStepCh() + + // NOTE: we have to send the vote for different block first so we don't just go into precommit round right + // away and ignore more prevotes (and thus fail to slash!) + + // add the conflicting vote + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + + // conflicting vote should cause us to broadcast dupeout tx on mempool + txs := cs1.mempoolReactor.Mempool.GetProposalTxs() + if len(txs) != 1 { + t.Fatal("expected to find a transaction in the mempool after double signing") + } + dupeoutTx, ok := txs[0].(*types.DupeoutTx) + if !ok { + t.Fatal("expected to find DupeoutTx in mempool after double signing") + } + + if !bytes.Equal(dupeoutTx.Address, cs2.privValidator.Address) { + t.Fatalf("expected DupeoutTx for %X, got %X", cs2.privValidator.Address, dupeoutTx.Address) + } + + // TODO: validate the sig +} + +func TestSlashingPrecommits(t *testing.T) { + css, _ := simpleConsensusState(2) + cs1, cs2 := css[0], css[1] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + + // add prevote from cs2 + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + + // wait to finish precommit + <-cs1.NewStepCh() + + // we should now be stuck in limbo forever, waiting for more prevotes + // add one for a different block should cause us to go into prevote wait + hash := cs1.ProposalBlock.Hash() + hash[0] = byte(hash[0]+1) % 255 + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + + // pass prevote wait + <-cs1.NewStepCh() + + // NOTE: we have to send the vote for different block first so we don't just go into precommit round right + // away and ignore more prevotes (and thus fail to slash!) + + // add precommit from cs2 + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + + // conflicting vote should cause us to broadcast dupeout tx on mempool + txs := cs1.mempoolReactor.Mempool.GetProposalTxs() + if len(txs) != 1 { + t.Fatal("expected to find a transaction in the mempool after double signing") + } + dupeoutTx, ok := txs[0].(*types.DupeoutTx) + if !ok { + t.Fatal("expected to find DupeoutTx in mempool after double signing") + } + + if !bytes.Equal(dupeoutTx.Address, cs2.privValidator.Address) { + t.Fatalf("expected DupeoutTx for %X, got %X", cs2.privValidator.Address, dupeoutTx.Address) + } + + // TODO: validate the sig + +} + +//------------------------------------------------------------------------------------------ +// CatchupSuite + +//------------------------------------------------------------------------------------------ +// HaltSuite + +// 4 vals. +// we receive a final precommit after going into next round, but others might have gone to commit already! +func TestHalt1(t *testing.T) { + css, privVals := simpleConsensusState(4) + cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[3] + cs1.newStepCh = make(chan *RoundState) // so it blocks + + timeoutChan := make(chan struct{}) + evsw := events.NewEventSwitch() + evsw.OnStart() + evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { + timeoutChan <- struct{}{} + }) + cs1.SetFireable(evsw) + + // start round and wait for propose and prevote + cs1.EnterNewRound(cs1.Height, 0, false) + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + + theBlockHash := cs1.ProposalBlock.Hash() + + donePrecommit := make(chan struct{}) + go func() { + <-cs1.NewStepCh() + donePrecommit <- struct{}{} + }() + signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs3, cs4) + <-donePrecommit + + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash) + + donePrecommitWait := make(chan struct{}) + go func() { + <-cs1.NewStepCh() + donePrecommitWait <- struct{}{} + }() + // add precommits from the rest + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, nil, types.PartSetHeader{}) // didnt receive proposal + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + // we receive this later, but cs3 might receive it earlier and with ours will go to commit! + precommit4 := signVote(cs4, types.VoteTypePrecommit, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + <-donePrecommitWait + + incrementRound(cs2, cs3, cs4) + + // timeout to new round + <-timeoutChan + + log.Info("### ONTO ROUND 2") + /*Round2 + // we timeout and prevote our lock + // a polka happened but we didn't see it! + */ + + // go to prevote, prevote for locked block + _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash()) + + // now we receive the precommit from the previous round + addVoteToFrom(cs1, cs4, precommit4) + + // receiving that precommit should take us straight to commit + ensureNewStep(t, cs1) + log.Warn("done enter commit!") + + // update to state + ensureNewStep(t, cs1) + + if cs1.Height != 2 { + t.Fatal("expected height to increment") + } +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/test.go index eb5ac9391afa748238672187cccd1df2d81e6aba..6c4b7d38a308f037bba8bc03df999417656e9e9f 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus/test.go @@ -8,6 +8,7 @@ import ( bc "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain" dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" mempl "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p" sm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state" @@ -206,6 +207,9 @@ func simpleConsensusState(nValidators int) ([]*ConsensusState, []*types.PrivVali cs := NewConsensusState(state, blockStore, mempoolReactor) cs.SetPrivValidator(privVals[i]) + evsw := events.NewEventSwitch() + cs.SetFireable(evsw) + // read off the NewHeightStep <-cs.NewStepCh() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/config.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/config.go new file mode 100644 index 0000000000000000000000000000000000000000..233bb88c2de02d6e4b61e803a9950893c5769d18 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/config.go @@ -0,0 +1,13 @@ +package mempool + +import ( + cfg "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/config" +) + +var config cfg.Config = nil + +func init() { + cfg.OnConfig(func(newConfig cfg.Config) { + config = newConfig + }) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool.go index c1b580f481742ec2a078a971e329749f5f372140..625b56010117b9dcc1783a339070884b1b2aeae3 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool.go @@ -19,7 +19,7 @@ type Mempool struct { mtx sync.Mutex state *sm.State cache *sm.BlockCache - txs []types.Tx + txs []types.Tx // TODO: we need to add a map to facilitate replace-by-fee } func NewMempool(state *sm.State) *Mempool { @@ -37,6 +37,12 @@ func (mem *Mempool) GetCache() *sm.BlockCache { return mem.cache } +func (mem *Mempool) GetHeight() int { + mem.mtx.Lock() + defer mem.mtx.Unlock() + return mem.state.LastBlockHeight +} + // Apply tx to the state and remember it. func (mem *Mempool) AddTx(tx types.Tx) (err error) { mem.mtx.Lock() @@ -59,11 +65,23 @@ func (mem *Mempool) GetProposalTxs() []types.Tx { return mem.txs } +// We use this to inform peer routines of how the mempool has been updated +type ResetInfo struct { + Height int + Included []Range + Invalid []Range +} + +type Range struct { + Start int + Length int +} + // "block" is the new block being committed. // "state" is the result of state.AppendBlock("block"). // Txs that are present in "block" are discarded from mempool. // Txs that have become invalid in the new "state" are also discarded. -func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) { +func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) ResetInfo { mem.mtx.Lock() defer mem.mtx.Unlock() mem.state = state.Copy() @@ -75,33 +93,51 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) { blockTxsMap[string(types.TxID(state.ChainID, tx))] = struct{}{} } - // Next, filter all txs from mem.txs that are in blockTxsMap - txs := []types.Tx{} - for _, tx := range mem.txs { + // Now we filter all txs from mem.txs that are in blockTxsMap, + // and ExecTx on what remains. Only valid txs are kept. + // We track the ranges of txs included in the block and invalidated by it + // so we can tell peer routines + var ri = ResetInfo{Height: block.Height} + var validTxs []types.Tx + includedStart, invalidStart := -1, -1 + for i, tx := range mem.txs { txID := types.TxID(state.ChainID, tx) if _, ok := blockTxsMap[string(txID)]; ok { + startRange(&includedStart, i) // start counting included txs + endRange(&invalidStart, i, &ri.Invalid) // stop counting invalid txs log.Info("Filter out, already committed", "tx", tx, "txID", txID) - continue - } else { - log.Info("Filter in, still new", "tx", tx, "txID", txID) - txs = append(txs, tx) - } - } - - // Next, filter all txs that aren't valid given new state. - validTxs := []types.Tx{} - for _, tx := range txs { - err := sm.ExecTx(mem.cache, tx, false, nil) - if err == nil { - log.Info("Filter in, valid", "tx", tx) - validTxs = append(validTxs, tx) } else { - // tx is no longer valid. - log.Info("Filter out, no longer valid", "tx", tx, "error", err) + endRange(&includedStart, i, &ri.Included) // stop counting included txs + err := sm.ExecTx(mem.cache, tx, false, nil) + if err != nil { + startRange(&invalidStart, i) // start counting invalid txs + log.Info("Filter out, no longer valid", "tx", tx, "error", err) + } else { + endRange(&invalidStart, i, &ri.Invalid) // stop counting invalid txs + log.Info("Filter in, new, valid", "tx", tx, "txID", txID) + validTxs = append(validTxs, tx) + } } } + endRange(&includedStart, len(mem.txs)-1, &ri.Included) // stop counting included txs + endRange(&invalidStart, len(mem.txs)-1, &ri.Invalid) // stop counting invalid txs // We're done! log.Info("New txs", "txs", validTxs, "oldTxs", mem.txs) mem.txs = validTxs + return ri +} + +func startRange(start *int, i int) { + if *start < 0 { + *start = i + } +} + +func endRange(start *int, i int, ranger *[]Range) { + if *start >= 0 { + length := i - *start + *ranger = append(*ranger, Range{*start, length}) + *start = -1 + } } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ab6e4e993dc27275b67a201fa6295c1a908986a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/mempool_test.go @@ -0,0 +1,273 @@ +package mempool + +import ( + "fmt" + "sync" + "testing" + "time" + + acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" + _ "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test" + sm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" +) + +var someAddr = []byte("ABCDEFGHIJABCDEFGHIJ") + +// number of txs +var nTxs = 100 + +// what the ResetInfo should look like after ResetForBlockAndState +var TestResetInfoData = ResetInfo{ + Included: []Range{ + Range{0, 5}, + Range{10, 10}, + Range{30, 5}, + }, + Invalid: []Range{ + Range{5, 5}, + Range{20, 8}, // let 28 and 29 be valid + Range{35, 64}, // let 99 be valid + }, +} + +// inverse of the ResetInfo +var notInvalidNotIncluded = map[int]struct{}{ + 28: struct{}{}, + 29: struct{}{}, + 99: struct{}{}, +} + +func newSendTx(t *testing.T, mempool *Mempool, from *acm.PrivAccount, to []byte, amt int64) types.Tx { + tx := types.NewSendTx() + tx.AddInput(mempool.GetCache(), from.PubKey, amt) + tx.AddOutput(to, amt) + tx.SignInput(config.GetString("chain_id"), 0, from) + if err := mempool.AddTx(tx); err != nil { + t.Fatal(err) + } + return tx +} + +func addTxs(t *testing.T, mempool *Mempool, lastAcc *acm.PrivAccount, privAccs []*acm.PrivAccount) []types.Tx { + txs := make([]types.Tx, nTxs) + for i := 0; i < nTxs; i++ { + if _, ok := notInvalidNotIncluded[i]; ok { + txs[i] = newSendTx(t, mempool, lastAcc, someAddr, 10) + } else { + txs[i] = newSendTx(t, mempool, privAccs[i%len(privAccs)], privAccs[(i+1)%len(privAccs)].Address, 5) + } + } + return txs +} + +func makeBlock(mempool *Mempool) *types.Block { + txs := mempool.GetProposalTxs() + var includedTxs []types.Tx + for _, rid := range TestResetInfoData.Included { + includedTxs = append(includedTxs, txs[rid.Start:rid.Start+rid.Length]...) + } + + mempool.mtx.Lock() + state := mempool.state + state.LastBlockHeight += 1 + mempool.mtx.Unlock() + return &types.Block{ + Header: &types.Header{ + ChainID: state.ChainID, + Height: state.LastBlockHeight, + NumTxs: len(includedTxs), + }, + Data: &types.Data{ + Txs: includedTxs, + }, + } +} + +// Add txs. Grab chunks to put in block. All the others become invalid because of nonce errors except those in notInvalidNotIncluded +func TestResetInfo(t *testing.T) { + amtPerAccount := int64(100000) + state, privAccs, _ := sm.RandGenesisState(6, false, amtPerAccount, 1, true, 100) + + mempool := NewMempool(state) + + lastAcc := privAccs[5] // we save him (his tx wont become invalid) + privAccs = privAccs[:5] + + txs := addTxs(t, mempool, lastAcc, privAccs) + + // its actually an invalid block since we're skipping nonces + // but all we care about is how the mempool responds after + block := makeBlock(mempool) + + ri := mempool.ResetForBlockAndState(block, state) + + if len(ri.Included) != len(TestResetInfoData.Included) { + t.Fatalf("invalid number of included ranges. Got %d, expected %d\n", len(ri.Included), len(TestResetInfoData.Included)) + } + + if len(ri.Invalid) != len(TestResetInfoData.Invalid) { + t.Fatalf("invalid number of invalid ranges. Got %d, expected %d\n", len(ri.Invalid), len(TestResetInfoData.Invalid)) + } + + for i, rid := range ri.Included { + inc := TestResetInfoData.Included[i] + if rid.Start != inc.Start { + t.Fatalf("Invalid start of range. Got %d, expected %d\n", inc.Start, rid.Start) + } + if rid.Length != inc.Length { + t.Fatalf("Invalid length of range. Got %d, expected %d\n", inc.Length, rid.Length) + } + } + + txs = mempool.GetProposalTxs() + if len(txs) != len(notInvalidNotIncluded) { + t.Fatalf("Expected %d txs left in mempool. Got %d", len(notInvalidNotIncluded), len(txs)) + } +} + +//------------------------------------------------------------------------------------------ + +type TestPeer struct { + sync.Mutex + running bool + height int + + t *testing.T + + received int + txs map[string]int + + timeoutFail int + + done chan int +} + +func newPeer(t *testing.T, state *sm.State) *TestPeer { + return &TestPeer{ + running: true, + height: state.LastBlockHeight, + t: t, + txs: make(map[string]int), + done: make(chan int), + } +} + +func (tp *TestPeer) IsRunning() bool { + tp.Lock() + defer tp.Unlock() + return tp.running +} + +func (tp *TestPeer) SetRunning(running bool) { + tp.Lock() + defer tp.Unlock() + tp.running = running +} + +func (tp *TestPeer) Send(chID byte, msg interface{}) bool { + if tp.timeoutFail > 0 { + time.Sleep(time.Second * time.Duration(tp.timeoutFail)) + return false + } + tx := msg.(*TxMessage).Tx + id := types.TxID(config.GetString("chain_id"), tx) + if _, ok := tp.txs[string(id)]; ok { + tp.t.Fatal("received the same tx twice!") + } + tp.txs[string(id)] = tp.received + tp.received += 1 + tp.done <- tp.received + return true +} + +func (tp *TestPeer) Get(key string) interface{} { + return tp +} + +func (tp *TestPeer) GetHeight() int { + return tp.height +} + +func TestBroadcast(t *testing.T) { + state, privAccs, _ := sm.RandGenesisState(6, false, 10000, 1, true, 100) + mempool := NewMempool(state) + reactor := NewMempoolReactor(mempool) + reactor.Start() + + lastAcc := privAccs[5] // we save him (his tx wont become invalid) + privAccs = privAccs[:5] + + peer := newPeer(t, state) + newBlockChan := make(chan ResetInfo) + tickerChan := make(chan time.Time) + go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer) + + // we don't broadcast any before updating + fmt.Println("dont broadcast any") + addTxs(t, mempool, lastAcc, privAccs) + block := makeBlock(mempool) + ri := mempool.ResetForBlockAndState(block, state) + newBlockChan <- ri + peer.height = ri.Height + tickerChan <- time.Now() + pullTxs(t, peer, len(mempool.txs)) // should have sent whatever txs are left (3) + + toBroadcast := []int{1, 3, 7, 9, 11, 12, 18, 20, 21, 28, 29, 30, 31, 34, 35, 36, 50, 90, 99, 100} + for _, N := range toBroadcast { + peer = resetPeer(t, reactor, mempool, state, tickerChan, newBlockChan, peer) + + // we broadcast N txs before updating + fmt.Println("broadcast", N) + addTxs(t, mempool, lastAcc, privAccs) + txsToSendPerCheck = N + tickerChan <- time.Now() + pullTxs(t, peer, txsToSendPerCheck) // should have sent N txs + block = makeBlock(mempool) + ri := mempool.ResetForBlockAndState(block, state) + newBlockChan <- ri + peer.height = ri.Height + txsToSendPerCheck = 100 + tickerChan <- time.Now() + left := len(mempool.txs) + if N > 99 { + left -= 3 + } else if N > 29 { + left -= 2 + } else if N > 28 { + left -= 1 + } + pullTxs(t, peer, left) // should have sent whatever txs are left that havent been sent + } +} + +func pullTxs(t *testing.T, peer *TestPeer, N int) { + timer := time.NewTicker(time.Second * 2) + for i := 0; i < N; i++ { + select { + case <-peer.done: + case <-timer.C: + panic(fmt.Sprintf("invalid number of received messages. Got %d, expected %d\n", i, N)) + } + } + + if N == 0 { + select { + case <-peer.done: + t.Fatalf("should not have sent any more txs") + case <-timer.C: + } + } +} + +func resetPeer(t *testing.T, reactor *MempoolReactor, mempool *Mempool, state *sm.State, tickerChan chan time.Time, newBlockChan chan ResetInfo, peer *TestPeer) *TestPeer { + // reset peer + mempool.txs = []types.Tx{} + mempool.state = state + mempool.cache = sm.NewBlockCache(state) + peer.SetRunning(false) + tickerChan <- time.Now() + peer = newPeer(t, state) + go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer) + return peer +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/reactor.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/reactor.go index fb3ff7d74cb6b7eaa235a5868fc0c527a2884b6c..01d714666a0c45a0432eff26bf6643e689470e1d 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/reactor.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/mempool/reactor.go @@ -2,18 +2,25 @@ package mempool import ( "bytes" + "errors" "fmt" "reflect" + "time" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p" + sm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/wire" ) var ( MempoolChannel = byte(0x30) + + checkExecutedTxsMilliseconds = 1 // check for new mempool txs to send to peer + txsToSendPerCheck = 64 // send up to this many txs from the mempool per check + newBlockChCapacity = 100 // queue to process this many ResetInfos per peer ) // MempoolReactor handles mempool tx broadcasting amongst peers. @@ -44,11 +51,17 @@ func (memR *MempoolReactor) GetChannels() []*p2p.ChannelDescriptor { } // Implements Reactor -func (pexR *MempoolReactor) AddPeer(peer *p2p.Peer) { +func (memR *MempoolReactor) AddPeer(peer *p2p.Peer) { + // Each peer gets a go routine on which we broadcast transactions in the same order we applied them to our state. + newBlockChan := make(chan ResetInfo, newBlockChCapacity) + peer.Data.Set(types.PeerMempoolChKey, newBlockChan) + timer := time.NewTicker(time.Millisecond * time.Duration(checkExecutedTxsMilliseconds)) + go memR.broadcastTxRoutine(timer.C, newBlockChan, peer) } // Implements Reactor -func (pexR *MempoolReactor) RemovePeer(peer *p2p.Peer, reason interface{}) { +func (memR *MempoolReactor) RemovePeer(peer *p2p.Peer, reason interface{}) { + // broadcast routine checks if peer is gone and returns } // Implements Reactor @@ -70,29 +83,128 @@ func (memR *MempoolReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte) { } else { log.Info("Added valid tx", "tx", msg.Tx) } - // Share tx. - // We use a simple shotgun approach for now. - // TODO: improve efficiency - for _, peer := range memR.Switch.Peers().List() { - if peer.Key == src.Key { + // broadcasting happens from go routines per peer + default: + log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) + } +} + +// "block" is the new block being committed. +// "state" is the result of state.AppendBlock("block"). +// Txs that are present in "block" are discarded from mempool. +// Txs that have become invalid in the new "state" are also discarded. +func (memR *MempoolReactor) ResetForBlockAndState(block *types.Block, state *sm.State) { + ri := memR.Mempool.ResetForBlockAndState(block, state) + for _, peer := range memR.Switch.Peers().List() { + peerMempoolCh := peer.Data.Get(types.PeerMempoolChKey).(chan ResetInfo) + select { + case peerMempoolCh <- ri: + default: + memR.Switch.StopPeerForError(peer, errors.New("Peer's mempool push channel full")) + } + } +} + +// Just an alias for AddTx since broadcasting happens in peer routines +func (memR *MempoolReactor) BroadcastTx(tx types.Tx) error { + return memR.Mempool.AddTx(tx) +} + +type PeerState interface { + GetHeight() int +} + +type Peer interface { + IsRunning() bool + Send(byte, interface{}) bool + Get(string) interface{} +} + +// send new mempool txs to peer, strictly in order we applied them to our state. +// new blocks take chunks out of the mempool, but we've already sent some txs to the peer. +// so we wait to hear that the peer has progressed to the new height, and then continue sending txs from where we left off +func (memR *MempoolReactor) broadcastTxRoutine(tickerChan <-chan time.Time, newBlockChan chan ResetInfo, peer Peer) { + var height = memR.Mempool.GetHeight() + var txsSent int // new txs sent for height. (reset every new height) + + for { + select { + case <-tickerChan: + if !peer.IsRunning() { + return + } + + // make sure the peer is up to date + if peerState_i := peer.Get(types.PeerStateKey); peerState_i != nil { + peerState := peerState_i.(PeerState) + if peerState.GetHeight() < height { + continue + } + } else { continue } - peer.TrySend(MempoolChannel, msg) + + // check the mempool for new transactions + newTxs := memR.getNewTxs(height) + txsSentLoop := 0 + start := time.Now() + + TX_LOOP: + for i := txsSent; i < len(newTxs) && txsSentLoop < txsToSendPerCheck; i++ { + tx := newTxs[i] + msg := &TxMessage{Tx: tx} + success := peer.Send(MempoolChannel, msg) + if !success { + break TX_LOOP + } else { + txsSentLoop += 1 + } + } + + if txsSentLoop > 0 { + txsSent += txsSentLoop + log.Info("Sent txs to peer", "txsSentLoop", txsSentLoop, + "took", time.Since(start), "txsSent", txsSent, "newTxs", len(newTxs)) + } + + case ri := <-newBlockChan: + height = ri.Height + + // find out how many txs below what we've sent were included in a block and how many became invalid + included := tallyRangesUpTo(ri.Included, txsSent) + invalidated := tallyRangesUpTo(ri.Invalid, txsSent) + + txsSent -= included + invalidated } + } +} - default: - log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) +// fetch new txs from the mempool +func (memR *MempoolReactor) getNewTxs(height int) (txs []types.Tx) { + memR.Mempool.mtx.Lock() + defer memR.Mempool.mtx.Unlock() + + // if the mempool got ahead of us just return empty txs + if memR.Mempool.state.LastBlockHeight != height { + return } + return memR.Mempool.txs } -func (memR *MempoolReactor) BroadcastTx(tx types.Tx) error { - err := memR.Mempool.AddTx(tx) - if err != nil { - return err +// return the size of ranges less than upTo +func tallyRangesUpTo(ranger []Range, upTo int) int { + totalUpTo := 0 + for _, r := range ranger { + if r.Start >= upTo { + break + } + if r.Start+r.Length >= upTo { + totalUpTo += upTo - r.Start + break + } + totalUpTo += r.Length } - msg := &TxMessage{Tx: tx} - memR.Switch.Broadcast(MempoolChannel, msg) - return nil + return totalUpTo } // implements events.Eventable diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go index c30a958ce03dbcf8b1912a8c3b17693dd0f64f9e..d35c5d8491c779418bcf5e74b1dcc6ca47816d60 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go @@ -2,10 +2,10 @@ package node import ( "bytes" + "io/ioutil" "math/rand" "net" "net/http" - "os" "strconv" "strings" "time" @@ -24,6 +24,7 @@ import ( sm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state" stypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/wire" ) @@ -44,7 +45,15 @@ type Node struct { privKey acm.PrivKeyEd25519 } -func NewNode() *Node { +func NewNodeDefaultPrivVal() *Node { + // Get PrivValidator + privValidatorFile := config.GetString("priv_validator_file") + privValidator := types.LoadOrGenPrivValidator(privValidatorFile) + + return NewNode(privValidator) +} + +func NewNode(privValidator *types.PrivValidator) *Node { // Get BlockStore blockStoreDB := dbm.GetDB("blockstore") blockStore := bc.NewBlockStore(blockStoreDB) @@ -74,20 +83,6 @@ func NewNode() *Node { // add the chainid to the global config config.Set("chain_id", state.ChainID) - // Get PrivValidator - var privValidator *types.PrivValidator - privValidatorFile := config.GetString("priv_validator_file") - if _, err := os.Stat(privValidatorFile); err == nil { - privValidator = types.LoadPrivValidator(privValidatorFile) - log.Notice("Loaded PrivValidator", - "file", privValidatorFile, "privValidator", privValidator) - } else { - privValidator = types.GenPrivValidator() - privValidator.SetFile(privValidatorFile) - privValidator.Save() - log.Notice("Generated PrivValidator", "file", privValidatorFile) - } - // Generate node PrivKey privKey := acm.GenPrivKeyEd25519() @@ -127,6 +122,17 @@ func NewNode() *Node { // they should all satisfy events.Eventable SetFireable(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor) + // run the profile server + profileHost := config.GetString("prof_laddr") + if profileHost != "" { + go func() { + log.Warn("Profile server", "error", http.ListenAndServe(profileHost, nil)) + }() + } + + // set vm log level + vm.SetDebug(config.GetBool("vm_log")) + return &Node{ sw: sw, evsw: eventSwitch, @@ -256,7 +262,7 @@ func makeNodeInfo(sw *p2p.Switch, privKey acm.PrivKeyEd25519) *types.NodeInfo { } // include git hash in the nodeInfo if available - if rev, err := ReadFile(config.GetString("revisions_file")); err == nil { + if rev, err := ReadFile(config.GetString("revision_file")); err == nil { nodeInfo.Version.Revision = string(rev) } @@ -286,8 +292,31 @@ func makeNodeInfo(sw *p2p.Switch, privKey acm.PrivKeyEd25519) *types.NodeInfo { //------------------------------------------------------------------------------ func RunNode() { + + // Wait until the genesis doc becomes available + genDocFile := config.GetString("genesis_file") + if !FileExists(genDocFile) { + log.Notice(Fmt("Waiting for genesis file %v...", genDocFile)) + for { + time.Sleep(time.Second) + if FileExists(genDocFile) { + break + } + jsonBlob, err := ioutil.ReadFile(genDocFile) + if err != nil { + Exit(Fmt("Couldn't read GenesisDoc file: %v", err)) + } + genDoc := stypes.GenesisDocFromJSON(jsonBlob) + if genDoc.ChainID == "" { + PanicSanity(Fmt("Genesis doc %v must include non-empty chain_id", genDocFile)) + } + config.Set("chain_id", genDoc.ChainID) + config.Set("genesis_doc", genDoc) + } + } + // Create & start node - n := NewNode() + n := NewNodeDefaultPrivVal() l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr")) n.AddListener(l) err := n.Start() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node_test.go index 01f601cf1a4a29288d1a3ba115acd5ec7340b5b3..84eb1014bd5e92e7bfb4655df3bbf29248bcabe7 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node_test.go @@ -10,7 +10,7 @@ import ( func TestNodeStartStop(t *testing.T) { // Create & start node - n := NewNode() + n := NewNodeDefaultPrivVal() l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr")) n.AddListener(l) n.Start() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/netaddress.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/netaddress.go index 8588ff73b82a3f2276698b1f8127a652f726e5c6..983cda622219e16c1c680dfb0e5b5d62ba254840 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/netaddress.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/netaddress.go @@ -110,6 +110,10 @@ func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { } func (na *NetAddress) Routable() bool { + if config.GetBool("local_routing") { + return na.Valid() + } + // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? return na.Valid() && !(na.RFC1918() || na.RFC3927() || na.RFC4862() || na.RFC4193() || na.RFC4843() || na.Local()) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/peer.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/peer.go index 82ff986b917618ac1f9b5c5e74a27e522d3e2570..7b291f80371e69647de9ca4b87e3be741f9f0d20 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/peer.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/peer.go @@ -128,3 +128,7 @@ func (p *Peer) String() string { func (p *Peer) Equals(other *Peer) bool { return p.Key == other.Key } + +func (p *Peer) Get(key string) interface{} { + return p.Data.Get(key) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/consensus.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/consensus.go index d0f3a71e52bd4e7d1aac6f6a68769331137014c4..4bec4e620a86cf98da448f49724155cff80e8bc4 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/consensus.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/consensus.go @@ -31,7 +31,7 @@ func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { peerRoundStates := []string{} for _, peer := range p2pSwitch.Peers().List() { // TODO: clean this up? - peerState := peer.Data.Get(cm.PeerStateKey).(*cm.PeerState) + peerState := peer.Data.Get(types.PeerStateKey).(*cm.PeerState) peerRoundState := peerState.GetRoundState() peerRoundStateStr := peer.Key + ":" + string(wire.JSONBytes(peerRoundState)) peerRoundStates = append(peerRoundStates, peerRoundStateStr) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/mempool.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/mempool.go index 61cd4b95ed0e592c09531515e7370db77e9c0039..c2c7f95f0842c6b095623332cc293955132b9a8d 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/mempool.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/mempool.go @@ -30,5 +30,6 @@ func BroadcastTx(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { } func ListUnconfirmedTxs() (*ctypes.ResultListUnconfirmedTxs, error) { - return &ctypes.ResultListUnconfirmedTxs{mempoolReactor.Mempool.GetProposalTxs()}, nil + txs := mempoolReactor.Mempool.GetProposalTxs() + return &ctypes.ResultListUnconfirmedTxs{len(txs), txs}, nil } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types/responses.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types/responses.go index bfc35523094de0ec21731046fac236a28fc845d3..45d4a2f305d1854efc5a612761465045e4cf3cfc 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types/responses.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types/responses.go @@ -98,6 +98,7 @@ type Receipt struct { } type ResultListUnconfirmedTxs struct { + N int `json:"n_txs"` Txs []types.Tx `json:"txs"` } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/test/helpers.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/test/helpers.go index 53c69a9bfcfc11625f90d71575fc7ded3b0bb014..5f3539711f515539f78dca2565bd17fca6e37bb9 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/test/helpers.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/test/helpers.go @@ -49,7 +49,7 @@ func makeUsers(n int) []*acm.PrivAccount { // create a new node and sleep forever func newNode(ready chan struct{}) { // Create & start node - node = nm.NewNode() + node = nm.NewNodeDefaultPrivVal() l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr")) node.AddListener(l) node.Start() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go index 4abbc242e6481bca9515cec4a7c6372d13e9d3e1..a5cfc458bde8ff1361aaa3202f910eea77614cbb 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go @@ -473,3 +473,11 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { nameReg: nameReg, } } + +func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*acm.PrivAccount, []*types.PrivValidator) { + db := dbm.NewMemDB() + genDoc, privAccounts, privValidators := RandGenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded) + s0 := MakeGenesisState(db, genDoc) + s0.Save() + return s0, privAccounts, privValidators +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go deleted file mode 100644 index ca6eae68c62a593e0d0292a26d5edb59a7a604f3..0000000000000000000000000000000000000000 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go +++ /dev/null @@ -1,76 +0,0 @@ -package state - -import ( - "sort" - "time" - - acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" - . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" - dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" - ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" - . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types" - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" -) - -func RandAccount(randBalance bool, minBalance int64) (*acm.Account, *acm.PrivAccount) { - privAccount := acm.GenPrivAccount() - perms := ptypes.DefaultAccountPermissions - acc := &acm.Account{ - Address: privAccount.PubKey.Address(), - PubKey: privAccount.PubKey, - Sequence: RandInt(), - Balance: minBalance, - Permissions: perms, - } - if randBalance { - acc.Balance += int64(RandUint32()) - } - return acc, privAccount -} - -func RandGenesisDoc(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*GenesisDoc, []*acm.PrivAccount, []*types.PrivValidator) { - accounts := make([]GenesisAccount, numAccounts) - privAccounts := make([]*acm.PrivAccount, numAccounts) - defaultPerms := ptypes.DefaultAccountPermissions - for i := 0; i < numAccounts; i++ { - account, privAccount := RandAccount(randBalance, minBalance) - accounts[i] = GenesisAccount{ - Address: account.Address, - Amount: account.Balance, - Permissions: &defaultPerms, // This will get copied into each state.Account. - } - privAccounts[i] = privAccount - } - validators := make([]GenesisValidator, numValidators) - privValidators := make([]*types.PrivValidator, numValidators) - for i := 0; i < numValidators; i++ { - valInfo, _, privVal := types.RandValidator(randBonded, minBonded) - validators[i] = GenesisValidator{ - PubKey: valInfo.PubKey, - Amount: valInfo.FirstBondAmount, - UnbondTo: []BasicAccount{ - { - Address: valInfo.PubKey.Address(), - Amount: valInfo.FirstBondAmount, - }, - }, - } - privValidators[i] = privVal - } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) - return &GenesisDoc{ - GenesisTime: time.Now(), - ChainID: "tendermint_test", - Accounts: accounts, - Validators: validators, - }, privAccounts, privValidators - -} - -func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*acm.PrivAccount, []*types.PrivValidator) { - db := dbm.NewMemDB() - genDoc, privAccounts, privValidators := RandGenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded) - s0 := MakeGenesisState(db, genDoc) - s0.Save() - return s0, privAccounts, privValidators -} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache_test.go index ef6544925948eab6ddaad9b681f4331bf2454cfa..ee8e96a058a61d51211cbed37de5a33b8a6b619f 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache_test.go @@ -4,11 +4,12 @@ import ( "bytes" "testing" + stypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/wire" ) func TestStateToFromVMAccount(t *testing.T) { - acmAcc1, _ := RandAccount(true, 456) + acmAcc1, _ := stypes.RandAccount(true, 456) vmAcc := toVMAccount(acmAcc1) acmAcc2 := toStateAccount(vmAcc) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types/genesis.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types/genesis.go index ed90b01bf6623ac4079194db7876d513480b9aef..d850fcb1a3a877b83384808fa3f4b449d2d551c4 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types/genesis.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types/genesis.go @@ -1,11 +1,13 @@ package types import ( + "sort" "time" acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/wire" ) @@ -59,3 +61,61 @@ func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) { } return } + +//------------------------------------------------------------ +// Make random genesis state + +func RandAccount(randBalance bool, minBalance int64) (*acm.Account, *acm.PrivAccount) { + privAccount := acm.GenPrivAccount() + perms := ptypes.DefaultAccountPermissions + acc := &acm.Account{ + Address: privAccount.PubKey.Address(), + PubKey: privAccount.PubKey, + Sequence: RandInt(), + Balance: minBalance, + Permissions: perms, + } + if randBalance { + acc.Balance += int64(RandUint32()) + } + return acc, privAccount +} + +func RandGenesisDoc(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*GenesisDoc, []*acm.PrivAccount, []*types.PrivValidator) { + accounts := make([]GenesisAccount, numAccounts) + privAccounts := make([]*acm.PrivAccount, numAccounts) + defaultPerms := ptypes.DefaultAccountPermissions + for i := 0; i < numAccounts; i++ { + account, privAccount := RandAccount(randBalance, minBalance) + accounts[i] = GenesisAccount{ + Address: account.Address, + Amount: account.Balance, + Permissions: &defaultPerms, // This will get copied into each state.Account. + } + privAccounts[i] = privAccount + } + validators := make([]GenesisValidator, numValidators) + privValidators := make([]*types.PrivValidator, numValidators) + for i := 0; i < numValidators; i++ { + valInfo, _, privVal := types.RandValidator(randBonded, minBonded) + validators[i] = GenesisValidator{ + PubKey: valInfo.PubKey, + Amount: valInfo.FirstBondAmount, + UnbondTo: []BasicAccount{ + { + Address: valInfo.PubKey.Address(), + Amount: valInfo.FirstBondAmount, + }, + }, + } + privValidators[i] = privVal + } + sort.Sort(types.PrivValidatorsByAddress(privValidators)) + return &GenesisDoc{ + GenesisTime: time.Now(), + ChainID: "tendermint_test", + Accounts: accounts, + Validators: validators, + }, privAccounts, privValidators + +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/wtf b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/wtf new file mode 100644 index 0000000000000000000000000000000000000000..0e10d8a5e714545eb97c2ae22cbf433a2eaaf7eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/wtf @@ -0,0 +1,68 @@ +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=send +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=call +[34mINFO[0m[07-22|18:13:12] Out account: Account{DFF89D4BE8CDDC7A1993C91A1C7FFE32D3778570:<nil> B:1536646667 C:120 S: P:{Base: 100011111110; Set: 11111111111111 []}} [34mmodule[0m=state +[34mINFO[0m[07-22|18:13:12] Calling contract 000000000000000000000000DFF89D4BE8CDDC7A1993C91A1C7FFE32D3778570 with code 60606040526000357C0100000000000000000000000000000000000000000000000000000000900480632FEE78D7146037576035565B005B6040600450606C565B604051808273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200191505060405180910390F35B60003390506075565B9056 [34mmodule[0m=state +[34mINFO[0m[07-22|18:13:12] Code for this contract: 60606040526000357C0100000000000000000000000000000000000000000000000000000000900480632FEE78D7146037576035565B005B6040600450606C565B604051808273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200191505060405180910390F35B60003390506075565B9056 [34mmodule[0m=state +(1) (00000000) 000000000000000000000000DFF89D4BE8CDDC7A1993C91A1C7FFE32D3778570 (code=120) gas: 1000 (d) 2FEE78D7 +(pc) 0 (op) PUSH1 (st) 0 => 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 2 (op) PUSH1 (st) 1 => 0x0000000000000000000000000000000000000000000000000000000000000040 +(pc) 4 (op) MSTORE (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 5 (op) PUSH1 (st) 0 => 0x0000000000000000000000000000000000000000000000000000000000000000 +(pc) 7 (op) CALLDATALOAD (st) 1 => 0x2FEE78D700000000000000000000000000000000000000000000000000000000 +(pc) 8 (op) PUSH29 (st) 1 => 0x0000000100000000000000000000000000000000000000000000000000000000 +(pc) 38 (op) SWAP1 (st) 2 => [2] 2FEE78D700000000000000000000000000000000000000000000000000000000 +(pc) 39 (op) DIV (st) 2 21680047490780924029029108358227327141478116379726555908761811030043504148480 / 26959946667150639794667015087019630673637144422540572481103610249216 = 804157655 (000000000000000000000000000000000000000000000000000000002FEE78D7) +(pc) 40 (op) DUP1 (st) 1 => [1] 0x000000000000000000000000000000000000000000000000000000002FEE78D7 +(pc) 41 (op) PUSH4 (st) 2 => 0x000000000000000000000000000000000000000000000000000000002FEE78D7 +(pc) 46 (op) EQ (st) 3 000000000000000000000000000000000000000000000000000000002FEE78D7 == 000000000000000000000000000000000000000000000000000000002FEE78D7 = 1 +(pc) 47 (op) PUSH1 (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000037 +(pc) 49 (op) JUMPI (st) 3 ~> 55 +(pc) 55 (op) JUMPDEST (st) 1 +(pc) 56 (op) PUSH1 (st) 1 => 0x0000000000000000000000000000000000000000000000000000000000000040 +(pc) 58 (op) PUSH1 (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000004 +(pc) 60 (op) POP (st) 3 => 10000000 +(pc) 61 (op) PUSH1 (st) 2 => 0x000000000000000000000000000000000000000000000000000000000000006C +(pc) 63 (op) JUMP (st) 3 ~> 108 +(pc) 108 (op) JUMPDEST (st) 2 +(pc) 109 (op) PUSH1 (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000000 +(pc) 111 (op) CALLER (st) 3 => 000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +(pc) 112 (op) SWAP1 (st) 4 => [2] 0000000000000000000000000000000000000000000000000000000000000000 +(pc) 113 (op) POP (st) 4 => 10000000 +(pc) 114 (op) PUSH1 (st) 3 => 0x0000000000000000000000000000000000000000000000000000000000000075 +(pc) 116 (op) JUMP (st) 4 ~> 117 +(pc) 117 (op) JUMPDEST (st) 3 +(pc) 118 (op) SWAP1 (st) 3 => [2] 0000000000000000000000000000000000000000000000000000000000000040 +(pc) 119 (op) JUMP (st) 3 ~> 64 +(pc) 64 (op) JUMPDEST (st) 2 +(pc) 65 (op) PUSH1 (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000040 +(pc) 67 (op) MLOAD (st) 3 => 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 68 (op) DUP1 (st) 3 => [1] 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 69 (op) DUP3 (st) 4 => [3] 0x000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +(pc) 70 (op) PUSH20 (st) 5 => 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +(pc) 91 (op) AND (st) 6 000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF & 000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 = 000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +(pc) 92 (op) DUP2 (st) 5 => [2] 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 93 (op) MSTORE (st) 6 => 0x000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +(pc) 94 (op) PUSH1 (st) 4 => 0x0000000000000000000000000000000000000000000000000000000000000020 +(pc) 96 (op) ADD (st) 5 32 + 96 = 128 (0000000000000000000000000000000000000000000000000000000000000080) +(pc) 97 (op) SWAP2 (st) 4 => [3] 000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +(pc) 98 (op) POP (st) 4 => 10000000 +(pc) 99 (op) POP (st) 3 => 10000000 +(pc) 100 (op) PUSH1 (st) 2 => 0x0000000000000000000000000000000000000000000000000000000000000040 +(pc) 102 (op) MLOAD (st) 3 => 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 103 (op) DUP1 (st) 3 => [1] 0x0000000000000000000000000000000000000000000000000000000000000060 +(pc) 104 (op) SWAP2 (st) 4 => [3] 0000000000000000000000000000000000000000000000000000000000000080 +(pc) 105 (op) SUB (st) 4 128 - 96 = 32 (0000000000000000000000000000000000000000000000000000000000000020) +(pc) 106 (op) SWAP1 (st) 3 => [2] 0000000000000000000000000000000000000000000000000000000000000060 +(pc) 107 (op) RETURN (st) 3 => [96, 32] (32) 0x000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 +[34mINFO[0m[07-22|18:13:12] Successful execution [34mmodule[0m=state +[32mNOTE[0m[07-22|18:13:12] VM call complete [32mmodule[0m=state [32mcaller[0m="VMAccount{000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 B:1529024268 C: N:1 S:0000000000000000000000000000000000000000000000000000000000000000}" [32mcallee[0m="VMAccount{000000000000000000000000DFF89D4BE8CDDC7A1993C91A1C7FFE32D3778570 B:1536646668 C:60606040526000357C0100000000000000000000000000000000000000000000000000000000900480632FEE78D7146037576035565B005B6040600450606C565B604051808273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200191505060405180910390F35B60003390506075565B9056 N:0 S:0000000000000000000000000000000000000000000000000000000000000000}" [32mreturn[0m=000000000000000000000000C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [32merr[0m=nil +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=name +[34mINFO[0m[07-22|18:13:12] New NameTx [34mmodule[0m=state [34mvalue[0m=10000 [34mcostPerBlock[0m=1237 [34mexpiresIn[0m=8 [34mlastBlock[0m=0 +[34mINFO[0m[07-22|18:13:12] Creating namereg entry [34mmodule[0m=state [34mname[0m=satoshi [34mexpiresIn[0m=8 +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=name +[34mINFO[0m[07-22|18:13:12] Invalid characters found in NameTx.Data (�€Èû). Only the kind of things found in a JSON file are allowed [34mmodule[0m=state +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=create_account +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=bond +[34mINFO[0m[07-22|18:13:12] Account has permission [34mmodule[0m=state [34maddress[0m=C91B248EFBD6841FF1A5E1615E4661C6293A84F6 [34mperm[0m=bond +PASS +ok github.com/tendermint/tendermint/state 0.311s diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/keys.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/keys.go new file mode 100644 index 0000000000000000000000000000000000000000..90591b95936e5dc6af8d616a099169f95c9ceca0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/keys.go @@ -0,0 +1,6 @@ +package types + +var ( + PeerStateKey = "ConsensusReactor.peerState" + PeerMempoolChKey = "MempoolReactor.peerMempoolCh" +) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/myfile b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/myfile new file mode 100644 index 0000000000000000000000000000000000000000..d4401eddd73a489b7f045232f80c08e4c7f2dc41 Binary files /dev/null and b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/myfile differ diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/node.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/node.go index e548a5779c46c7e2515bd67355705f4ee9613250..8e320cba28f249fc9c44e357f474ab21273773fe 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/node.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/node.go @@ -19,7 +19,7 @@ type NodeInfo struct { type Versions struct { Revision string `json:"revision"` - Tendermint string `json"tendermint"` + Tendermint string `json:"tendermint"` P2P string `json:"p2p"` RPC string `json:"rpc"` Wire string `json:"wire"` diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/priv_validator.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/priv_validator.go index aad8d118c9561fd4931e89b77f71fbef5036154b..ee6f9e27c9cee45d37ff30cea28b544fb801af3e 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/priv_validator.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/priv_validator.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "math" + "os" "sync" acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" @@ -35,12 +36,15 @@ func voteToStep(vote *Vote) int8 { } type PrivValidator struct { - Address []byte `json:"address"` - PubKey acm.PubKeyEd25519 `json:"pub_key"` - PrivKey acm.PrivKeyEd25519 `json:"priv_key"` - LastHeight int `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` + Address []byte `json:"address"` + PubKey acm.PubKeyEd25519 `json:"pub_key"` + LastHeight int `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + + // NOTE: A safer implemention leaves PrivKey empty and uses a secure key signing service. + PrivKey acm.PrivKeyEd25519 `json:"priv_key"` + signer Signer // For persistence. // Overloaded for testing. @@ -48,6 +52,28 @@ type PrivValidator struct { mtx sync.Mutex } +type Signer interface { + Sign(msg []byte) acm.SignatureEd25519 +} + +// Implements Signer +type DefaultSigner struct { + priv acm.PrivKeyEd25519 +} + +func NewDefaultSigner(priv acm.PrivKeyEd25519) *DefaultSigner { + return &DefaultSigner{priv: priv} +} + +// Implements Signer +func (ds *DefaultSigner) Sign(msg []byte) acm.SignatureEd25519 { + return ds.priv.Sign(msg).(acm.SignatureEd25519) +} + +func (privVal *PrivValidator) SetSigner(s Signer) { + privVal.signer = s +} + // Generates a new validator with private key. func GenPrivValidator() *PrivValidator { privKeyBytes := new([64]byte) @@ -63,6 +89,7 @@ func GenPrivValidator() *PrivValidator { LastRound: 0, LastStep: stepNone, filePath: "", + signer: NewDefaultSigner(privKey), } } @@ -76,9 +103,25 @@ func LoadPrivValidator(filePath string) *PrivValidator { Exit(Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) } privVal.filePath = filePath + privVal.signer = NewDefaultSigner(privVal.PrivKey) return privVal } +func LoadOrGenPrivValidator(filePath string) *PrivValidator { + var privValidator *PrivValidator + if _, err := os.Stat(filePath); err == nil { + privValidator = LoadPrivValidator(filePath) + log.Notice("Loaded PrivValidator", + "file", filePath, "privValidator", privValidator) + } else { + privValidator = GenPrivValidator() + privValidator.SetFile(filePath) + privValidator.Save() + log.Notice("Generated PrivValidator", "file", filePath) + } + return privValidator +} + func (privVal *PrivValidator) SetFile(filePath string) { privVal.mtx.Lock() defer privVal.mtx.Unlock() @@ -130,14 +173,10 @@ func (privVal *PrivValidator) SignVote(chainID string, vote *Vote) error { privVal.save() // Sign - privVal.SignVoteUnsafe(chainID, vote) + vote.Signature = privVal.signer.Sign(acm.SignBytes(chainID, vote)) return nil } -func (privVal *PrivValidator) SignVoteUnsafe(chainID string, vote *Vote) { - vote.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, vote)).(acm.SignatureEd25519) -} - func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() @@ -152,7 +191,7 @@ func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) e privVal.save() // Sign - proposal.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, proposal)).(acm.SignatureEd25519) + proposal.Signature = privVal.signer.Sign(acm.SignBytes(chainID, proposal)) return nil } else { return errors.New(fmt.Sprintf("Attempt of duplicate signing of proposal: Height %v, Round %v", proposal.Height, proposal.Round)) @@ -172,7 +211,7 @@ func (privVal *PrivValidator) SignRebondTx(chainID string, rebondTx *RebondTx) e privVal.save() // Sign - rebondTx.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, rebondTx)).(acm.SignatureEd25519) + rebondTx.Signature = privVal.signer.Sign(acm.SignBytes(chainID, rebondTx)) return nil } else { return errors.New(fmt.Sprintf("Attempt of duplicate signing of rebondTx: Height %v", rebondTx.Height)) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set.go index a1c551e9202f7b7fcf16fdaadd8b42ab143c7f5e..4aaa772fc0ca4d85ed34912f2260cdcf96083c7f 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set.go @@ -204,6 +204,9 @@ func (voteSet *VoteSet) IsCommit() bool { if voteSet == nil { return false } + if voteSet.type_ != VoteTypePrecommit { + return false + } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() return len(voteSet.maj23Hash) > 0 diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set_test.go index 10a42f31e67c964fa91075945f7550e9b25fd59c..137d73c3897e9ac282dd3dcc6a6c713fb236613d 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/types/vote_set_test.go @@ -4,6 +4,7 @@ import ( "bytes" "sort" + acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common/test" _ "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/config/tendermint_test" @@ -60,7 +61,7 @@ func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote { } func signAddVote(privVal *PrivValidator, vote *Vote, voteSet *VoteSet) (bool, error) { - privVal.SignVoteUnsafe(config.GetString("chain_id"), vote) + vote.Signature = privVal.signer.Sign(acm.SignBytes(config.GetString("chain_id"), vote)) added, _, err := voteSet.AddByAddress(privVal.Address, vote) return added, err } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go index 899bb2b7b8b27e57903d42b2a9a03af20ae3a891..900a3ff4dded1fc950dc8a62c05cbf36735e1122 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go @@ -69,6 +69,37 @@ func TestVM(t *testing.T) { } } +func TestJumpErr(t *testing.T) { + ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + + // Create accounts + account1 := &Account{ + Address: Int64ToWord256(100), + } + account2 := &Account{ + Address: Int64ToWord256(101), + } + + var gas int64 = 100000 + code := []byte{0x60, 0x10, 0x56} // jump to position 16, a clear failure + var output []byte + var err error + ch := make(chan struct{}) + go func() { + output, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + ch <- struct{}{} + }() + tick := time.NewTicker(time.Second * 2) + select { + case <-tick.C: + t.Fatal("VM ended up in an infinite loop from bad jump dest (it took too long!)") + case <-ch: + if err == nil { + t.Fatal("Expected invalid jump dest err") + } + } +} + // Tests the code for a subcurrency contract compiled by serpent func TestSubcurrency(t *testing.T) { st := newAppState() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go index 38ce23f7919d9bf453d2e40945314ef0a6b5825a..1af1735035f95080cf61709b3c5ac7633c44d64f 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go @@ -36,15 +36,20 @@ func (err ErrPermission) Error() string { return fmt.Sprintf("Contract does not have permission to %s", err.typ) } -type Debug bool - const ( - dataStackCapacity = 1024 - callStackCapacity = 100 // TODO ensure usage. - memoryCapacity = 1024 * 1024 // 1 MB - dbg Debug = true + dataStackCapacity = 1024 + callStackCapacity = 100 // TODO ensure usage. + memoryCapacity = 1024 * 1024 // 1 MB ) +type Debug bool + +var dbg Debug + +func SetDebug(d bool) { + dbg = Debug(d) +} + func (d Debug) Printf(s string, a ...interface{}) { if d { fmt.Printf(s, a...) @@ -109,6 +114,8 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *A } // CONTRACT appState is aware of caller and callee, so we can just mutate them. +// CONTRACT code and input are not mutated. +// CONTRACT returned 'ret' is a new compact slice. // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. // code: May be nil, since the CALL opcode may be used to send value from contracts to accounts @@ -632,13 +639,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 - err = jump(code, stack.Pop64(), &pc) + if err = jump(code, stack.Pop64(), &pc); err != nil { + return nil, err + } continue case JUMPI: // 0x57 pos, cond := stack.Pop64(), stack.Pop() if !cond.IsZero() { - err = jump(code, pos, &pc) + if err = jump(code, pos, &pc); err != nil { + return nil, err + } continue } dbg.Printf(" ~> false\n") @@ -691,6 +702,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) } + data = copyslice(data) if vm.evc != nil { eventID := types.EventStringLogEvent(callee.Address.Postfix(20)) fmt.Printf("eventID: %s\n", eventID) @@ -724,11 +736,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas newAccount := vm.appState.CreateAccount(callee) // Run the input to get the contract code. + // NOTE: no need to copy 'input' as per Call contract. ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas) if err_ != nil { stack.Push(Zero256) } else { - newAccount.Code = ret // Set the code + newAccount.Code = ret // Set the code (ret need not be copied as per Call contract) stack.Push(newAccount.Address) } @@ -747,6 +760,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) } + args = copyslice(args) // Ensure that gasLimit is reasonable if *gas < gasLimit { @@ -827,7 +841,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas return nil, firstErr(err, ErrMemoryOutOfBounds) } dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) - return ret, nil + output = copyslice(ret) + return output, nil case SUICIDE: // 0xFF addr := stack.Pop() @@ -870,10 +885,15 @@ func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { } else { ret, ok = data[offset:offset+length], true } - return } +func copyslice(src []byte) (dest []byte) { + dest = make([]byte, len(src)) + copy(dest, src) + return dest +} + func rightMostBytes(data []byte, n int) []byte { size := MinInt(len(data), n) offset := len(data) - size diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/wire/util.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/wire/util.go index 91aa6f5c31526aec683c6346381c766ebfe7b836..bfe32d726599f7acc2b1730ba950fa8ec335678b 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/wire/util.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/wire/util.go @@ -3,6 +3,8 @@ package wire import ( "bytes" "crypto/sha256" + "encoding/json" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/code.google.com/p/go.crypto/ripemd160" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" @@ -26,6 +28,21 @@ func JSONBytes(o interface{}) []byte { return w.Bytes() } +// NOTE: inefficient +func JSONBytesPretty(o interface{}) []byte { + jsonBytes := JSONBytes(o) + var object interface{} + err := json.Unmarshal(jsonBytes, &object) + if err != nil { + PanicSanity(err) + } + jsonBytes, err = json.MarshalIndent(object, "", "\t") + if err != nil { + PanicSanity(err) + } + return jsonBytes +} + // NOTE: does not care about the type, only the binary representation. func BinaryEqual(a, b interface{}) bool { aBytes := BinaryBytes(a)