diff --git a/common/math/integral/integral_math.go b/common/math/integral/integral_math.go
new file mode 100644
index 0000000000000000000000000000000000000000..8cdc6ee18d3a4a1e8ed777527d9605d325a4a61c
--- /dev/null
+++ b/common/math/integral/integral_math.go
@@ -0,0 +1,157 @@
+package integral
+
+func MaxInt8(a, b int8) int8 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxUint8(a, b uint8) uint8 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxInt16(a, b int16) int16 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxUint16(a, b uint16) uint16 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxInt32(a, b int32) int32 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxUint32(a, b uint32) uint32 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxInt64(a, b int64) int64 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxUint64(a, b uint64) uint64 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxInt(a, b int) int {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func MaxUint(a, b uint) uint {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+//-----------------------------------------------------------------------------
+
+func MinInt8(a, b int8) int8 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinUint8(a, b uint8) uint8 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinInt16(a, b int16) int16 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinUint16(a, b uint16) uint16 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinInt32(a, b int32) int32 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinUint32(a, b uint32) uint32 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinInt64(a, b int64) int64 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinUint64(a, b uint64) uint64 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinInt(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func MinUint(a, b uint) uint {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+//-----------------------------------------------------------------------------
+
+func ExpUint64(a, b uint64) uint64 {
+	accum := uint64(1)
+	for b > 0 {
+		if b&1 == 1 {
+			accum *= a
+		}
+		a *= a
+		b >>= 1
+	}
+	return accum
+}
diff --git a/definitions/pipe.go b/definitions/pipe.go
index f2f7ba8057650c8ad21bdf2b1bf3d244b6ccd524..f143e8a6465567c41da0c88d298773431b04f3cf 100644
--- a/definitions/pipe.go
+++ b/definitions/pipe.go
@@ -103,6 +103,5 @@ type Transactor interface {
 	TransactNameReg(privKey []byte, name, data string, amount,
 		fee int64) (*txs.Receipt, error)
 	UnconfirmedTxs() (*txs.UnconfirmedTxs, error)
-	SignTx(tx txs.Tx,
-		privAccounts []*account.PrivAccount) (txs.Tx, error)
+	SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error)
 }
diff --git a/definitions/tendermint_pipe.go b/definitions/tendermint_pipe.go
index 01d5d849827ae233d70302012ce69e41cf1fb2bd..76e7a2a617d8ed39b865f435d5acf2d532a450d5 100644
--- a/definitions/tendermint_pipe.go
+++ b/definitions/tendermint_pipe.go
@@ -17,8 +17,8 @@
 package definitions
 
 import (
-	account "github.com/eris-ltd/eris-db/account"
-	rpc_tendermint_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
+	"github.com/eris-ltd/eris-db/account"
+	rpc_tm_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
 	"github.com/eris-ltd/eris-db/txs"
 )
 
@@ -28,48 +28,45 @@ import (
 // collection of RPC routes for Eris-DB-1.0.0
 
 type TendermintPipe interface {
-	// Net
-	Status() (*rpc_tendermint_types.ResultStatus, error)
-
-	NetInfo() (*rpc_tendermint_types.ResultNetInfo, error)
+	Pipe
+	// Events
+	// Subscribe attempts to subscribe the listener identified by listenerId to
+	// the event named event. The Event result is written to rpcResponseWriter
+	// which must be non-blocking
+	Subscribe(listenerId, event string,
+		rpcResponseWriter func(result rpc_tm_types.ErisDBResult)) (*rpc_tm_types.ResultSubscribe, error)
+	Unsubscribe(listenerId, event string) (*rpc_tm_types.ResultUnsubscribe, error)
 
-	Genesis() (*rpc_tendermint_types.ResultGenesis, error)
+	// Net
+	Status() (*rpc_tm_types.ResultStatus, error)
+	NetInfo() (*rpc_tm_types.ResultNetInfo, error)
+	Genesis() (*rpc_tm_types.ResultGenesis, error)
 
 	// Accounts
-	GetAccount(address []byte) (*rpc_tendermint_types.ResultGetAccount,
-		error)
-
-	ListAccounts() (*rpc_tendermint_types.ResultListAccounts, error)
-
-	GetStorage(address, key []byte) (*rpc_tendermint_types.ResultGetStorage,
-		error)
-
-	DumpStorage(address []byte) (*rpc_tendermint_types.ResultDumpStorage,
-		error)
+	GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount, error)
+	ListAccounts() (*rpc_tm_types.ResultListAccounts, error)
+	GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage, error)
+	DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage, error)
 
 	// Call
-	Call(fromAddress, toAddress, data []byte) (*rpc_tendermint_types.ResultCall,
-		error)
-
-	CallCode(fromAddress, code, data []byte) (*rpc_tendermint_types.ResultCall,
-		error)
+	Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, error)
+	CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall, error)
 
 	// TODO: [ben] deprecate as we should not allow unsafe behaviour
 	// where a user is allowed to send a private key over the wire,
 	// especially unencrypted.
 	SignTransaction(tx txs.Tx,
-		privAccounts []*account.PrivAccount) (*rpc_tendermint_types.ResultSignTx,
+		privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx,
 		error)
 
 	// Name registry
-	GetName(name string) (*rpc_tendermint_types.ResultGetName, error)
-
-	ListNames() (*rpc_tendermint_types.ResultListNames, error)
+	GetName(name string) (*rpc_tm_types.ResultGetName, error)
+	ListNames() (*rpc_tm_types.ResultListNames, error)
 
 	// Memory pool
-	BroadcastTxAsync(transaction txs.Tx) (*rpc_tendermint_types.ResultBroadcastTx,
-		error)
+	BroadcastTxAsync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error)
+	BroadcastTxSync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error)
 
-	BroadcastTxSync(transaction txs.Tx) (*rpc_tendermint_types.ResultBroadcastTx,
-		error)
+	// Blockchain
+	BlockchainInfo(minHeight, maxHeight, maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error)
 }
diff --git a/event/event_cache.go b/event/event_cache.go
index 5b196e16eb3d12eeb9c672a6e3965c79a138a72f..326cf9b1c56b7792e952c225f5d0beb23606e1ff 100644
--- a/event/event_cache.go
+++ b/event/event_cache.go
@@ -80,7 +80,7 @@ func reap(es *EventSubscriptions) {
 }
 
 // Add a subscription and return the generated id. Note event dispatcher
-// has to call func which involves aquiring a mutex lock, so might be
+// has to call func which involves acquiring a mutex lock, so might be
 // a delay - though a conflict is practically impossible, and if it does
 // happen it's for an insignificant amount of time (the time it takes to
 // carry out EventCache.poll() ).
diff --git a/event/events.go b/event/events.go
index 8861056cb40f4bb501c6325870a33c344d6e6494..dd5f47f6a41e9ee1ffbdcf0b1a96db2f113c7b84 100644
--- a/event/events.go
+++ b/event/events.go
@@ -39,7 +39,7 @@ type events struct {
 	eventSwitch *evts.EventSwitch
 }
 
-func newEvents(eventSwitch *evts.EventSwitch) *events {
+func NewEvents(eventSwitch *evts.EventSwitch) *events {
 	return &events{eventSwitch}
 }
 
diff --git a/manager/eris-mint/events.go b/manager/eris-mint/events.go
deleted file mode 100644
index c7d582aecb6fc42c2d11114b695c7cbabcbb1f22..0000000000000000000000000000000000000000
--- a/manager/eris-mint/events.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2015, 2016 Eris Industries (UK) Ltd.
-// This file is part of Eris-RT
-
-// Eris-RT is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Eris-RT is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.
-
-// Events is part of the pipe for ErisMint and provides the implementation
-// for the pipe to call into the ErisMint application
-package erismint
-
-import (
-	evts "github.com/tendermint/go-events"
-)
-
-// TODO improve
-
-// The events struct has methods for working with events.
-type events struct {
-	eventSwitch *evts.EventSwitch
-}
-
-func newEvents(eventSwitch *evts.EventSwitch) *events {
-	return &events{eventSwitch}
-}
-
-// Subscribe to an event.
-func (this *events) Subscribe(subId, event string, callback func(evts.EventData)) (bool, error) {
-	this.eventSwitch.AddListenerForEvent(subId, event, callback)
-	return true, nil
-}
-
-// Un-subscribe from an event.
-func (this *events) Unsubscribe(subId string) (bool, error) {
-	this.eventSwitch.RemoveListener(subId)
-	return true, nil
-}
diff --git a/manager/eris-mint/pipe.go b/manager/eris-mint/pipe.go
index 5741617145937691d8b0579c2e561703d7e0cb47..46e5e57fad0c8e5168c7da17f814c176e752d0b4 100644
--- a/manager/eris-mint/pipe.go
+++ b/manager/eris-mint/pipe.go
@@ -20,38 +20,39 @@ import (
 	"bytes"
 	"fmt"
 
-	tendermint_common "github.com/tendermint/go-common"
+	tm_common "github.com/tendermint/go-common"
 	crypto "github.com/tendermint/go-crypto"
 	db "github.com/tendermint/go-db"
-	tendermint_events "github.com/tendermint/go-events"
+	go_events "github.com/tendermint/go-events"
 	wire "github.com/tendermint/go-wire"
-	tendermint_types "github.com/tendermint/tendermint/types"
+	tm_types "github.com/tendermint/tendermint/types"
 	tmsp_types "github.com/tendermint/tmsp/types"
 
 	log "github.com/eris-ltd/eris-logger"
 
 	account "github.com/eris-ltd/eris-db/account"
+	imath "github.com/eris-ltd/eris-db/common/math/integral"
 	config "github.com/eris-ltd/eris-db/config"
 	core_types "github.com/eris-ltd/eris-db/core/types"
 	definitions "github.com/eris-ltd/eris-db/definitions"
-	event "github.com/eris-ltd/eris-db/event"
+	edb_event "github.com/eris-ltd/eris-db/event"
 	vm "github.com/eris-ltd/eris-db/manager/eris-mint/evm"
 	state "github.com/eris-ltd/eris-db/manager/eris-mint/state"
 	state_types "github.com/eris-ltd/eris-db/manager/eris-mint/state/types"
 	manager_types "github.com/eris-ltd/eris-db/manager/types"
-	rpc_tendermint_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
+	rpc_tm_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
 	"github.com/eris-ltd/eris-db/txs"
 )
 
 type ErisMintPipe struct {
 	erisMintState *state.State
-	eventSwitch   *tendermint_events.EventSwitch
+	eventSwitch   *go_events.EventSwitch
 	erisMint      *ErisMint
 	// Pipe implementations
 	accounts   *accounts
 	blockchain *blockchain
 	consensus  *consensus
-	events     event.EventEmitter
+	events     edb_event.EventEmitter
 	namereg    *namereg
 	network    *network
 	transactor *transactor
@@ -71,7 +72,7 @@ var _ definitions.Pipe = (*ErisMintPipe)(nil)
 var _ definitions.TendermintPipe = (*ErisMintPipe)(nil)
 
 func NewErisMintPipe(moduleConfig *config.ModuleConfig,
-	eventSwitch *tendermint_events.EventSwitch) (*ErisMintPipe, error) {
+	eventSwitch *go_events.EventSwitch) (*ErisMintPipe, error) {
 
 	startedState, genesisDoc, err := startState(moduleConfig.DataDir,
 		moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile,
@@ -95,7 +96,7 @@ func NewErisMintPipe(moduleConfig *config.ModuleConfig,
 	erisMint.SetHostAddress(tendermintHost)
 
 	// initialise the components of the pipe
-	events := newEvents(eventSwitch)
+	events := edb_event.NewEvents(eventSwitch)
 	accounts := newAccounts(erisMint)
 	namereg := newNameReg(erisMint)
 	transactor := newTransactor(moduleConfig.ChainId, eventSwitch, erisMint,
@@ -186,7 +187,7 @@ func (pipe *ErisMintPipe) Consensus() definitions.Consensus {
 	return pipe.consensus
 }
 
-func (pipe *ErisMintPipe) Events() event.EventEmitter {
+func (pipe *ErisMintPipe) Events() edb_event.EventEmitter {
 	return pipe.events
 }
 
@@ -227,8 +228,33 @@ func (pipe *ErisMintPipe) GetTendermintPipe() (definitions.TendermintPipe,
 
 //------------------------------------------------------------------------------
 // Implement definitions.TendermintPipe for ErisMintPipe
+func (pipe *ErisMintPipe) Subscribe(listenerId, event string,
+	rpcResponseWriter func(result rpc_tm_types.ErisDBResult)) (*rpc_tm_types.ResultSubscribe, error) {
+	log.WithFields(log.Fields{"listenerId": listenerId, "event": event}).
+		Info("Subscribing to event")
+	pipe.events.Subscribe(subscriptionId(listenerId, event), event,
+		func(eventData go_events.EventData) {
+			result := rpc_tm_types.ErisDBResult(&rpc_tm_types.ResultEvent{event,
+				tm_types.TMEventData(eventData)})
+			// NOTE: EventSwitch callbacks must be nonblocking
+			rpcResponseWriter(result)
+		})
+	return &rpc_tm_types.ResultSubscribe{}, nil
+}
+
+func (pipe *ErisMintPipe) Unsubscribe(listenerId,
+	event string) (*rpc_tm_types.ResultUnsubscribe, error) {
+	log.WithFields(log.Fields{"listenerId": listenerId, "event": event}).
+			Info("Unsubsribing from event")
+	pipe.events.Unsubscribe(subscriptionId(listenerId, event))
+	return &rpc_tm_types.ResultUnsubscribe{}, nil
+}
+
+func subscriptionId(listenerId, event string) string {
+	return fmt.Sprintf("%s#%s", listenerId, event)
+}
 
-func (pipe *ErisMintPipe) Status() (*rpc_tendermint_types.ResultStatus, error) {
+func (pipe *ErisMintPipe) Status() (*rpc_tm_types.ResultStatus, error) {
 	memoryDatabase := db.NewMemDB()
 	if pipe.genesisState == nil {
 		pipe.genesisState = state.MakeGenesisState(memoryDatabase, pipe.genesisDoc)
@@ -239,7 +265,7 @@ func (pipe *ErisMintPipe) Status() (*rpc_tendermint_types.ResultStatus, error) {
 	}
 	latestHeight := pipe.consensusEngine.Height()
 	var (
-		latestBlockMeta *tendermint_types.BlockMeta
+		latestBlockMeta *tm_types.BlockMeta
 		latestBlockHash []byte
 		latestBlockTime int64
 	)
@@ -248,7 +274,7 @@ func (pipe *ErisMintPipe) Status() (*rpc_tendermint_types.ResultStatus, error) {
 		latestBlockHash = latestBlockMeta.Hash
 		latestBlockTime = latestBlockMeta.Header.Time.UnixNano()
 	}
-	return &rpc_tendermint_types.ResultStatus{
+	return &rpc_tm_types.ResultStatus{
 		NodeInfo:          pipe.consensusEngine.NodeInfo(),
 		GenesisHash:       genesisHash,
 		PubKey:            pipe.consensusEngine.PublicValidatorKey(),
@@ -257,41 +283,41 @@ func (pipe *ErisMintPipe) Status() (*rpc_tendermint_types.ResultStatus, error) {
 		LatestBlockTime:   latestBlockTime}, nil
 }
 
-func (pipe *ErisMintPipe) NetInfo() (*rpc_tendermint_types.ResultNetInfo, error) {
+func (pipe *ErisMintPipe) NetInfo() (*rpc_tm_types.ResultNetInfo, error) {
 	listening := pipe.consensusEngine.IsListening()
 	listeners := []string{}
 	for _, listener := range pipe.consensusEngine.Listeners() {
 		listeners = append(listeners, listener.String())
 	}
 	peers := pipe.consensusEngine.Peers()
-	return &rpc_tendermint_types.ResultNetInfo{
+	return &rpc_tm_types.ResultNetInfo{
 		Listening: listening,
 		Listeners: listeners,
 		Peers:     peers,
 	}, nil
 }
 
-func (pipe *ErisMintPipe) Genesis() (*rpc_tendermint_types.ResultGenesis, error) {
-	return &rpc_tendermint_types.ResultGenesis{
+func (pipe *ErisMintPipe) Genesis() (*rpc_tm_types.ResultGenesis, error) {
+	return &rpc_tm_types.ResultGenesis{
 		// TODO: [ben] sharing pointer to unmutated GenesisDoc, but is not immutable
 		Genesis: pipe.genesisDoc,
 	}, nil
 }
 
 // Accounts
-func (pipe *ErisMintPipe) GetAccount(address []byte) (*rpc_tendermint_types.ResultGetAccount,
+func (pipe *ErisMintPipe) GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount,
 	error) {
 	cache := pipe.erisMint.GetCheckCache()
 	// cache := mempoolReactor.Mempool.GetCache()
 	account := cache.GetAccount(address)
 	if account == nil {
 		log.Warn("Nil Account")
-		return &rpc_tendermint_types.ResultGetAccount{nil}, nil
+		return &rpc_tm_types.ResultGetAccount{nil}, nil
 	}
-	return &rpc_tendermint_types.ResultGetAccount{account}, nil
+	return &rpc_tm_types.ResultGetAccount{account}, nil
 }
 
-func (pipe *ErisMintPipe) ListAccounts() (*rpc_tendermint_types.ResultListAccounts, error) {
+func (pipe *ErisMintPipe) ListAccounts() (*rpc_tm_types.ResultListAccounts, error) {
 	var blockHeight int
 	var accounts []*account.Account
 	state := pipe.erisMint.GetState()
@@ -300,10 +326,10 @@ func (pipe *ErisMintPipe) ListAccounts() (*rpc_tendermint_types.ResultListAccoun
 		accounts = append(accounts, account.DecodeAccount(value))
 		return false
 	})
-	return &rpc_tendermint_types.ResultListAccounts{blockHeight, accounts}, nil
+	return &rpc_tm_types.ResultListAccounts{blockHeight, accounts}, nil
 }
 
-func (pipe *ErisMintPipe) GetStorage(address, key []byte) (*rpc_tendermint_types.ResultGetStorage,
+func (pipe *ErisMintPipe) GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage,
 	error) {
 	state := pipe.erisMint.GetState()
 	// state := consensusState.GetState()
@@ -315,14 +341,15 @@ func (pipe *ErisMintPipe) GetStorage(address, key []byte) (*rpc_tendermint_types
 	storageTree := state.LoadStorage(storageRoot)
 
 	_, value, exists := storageTree.Get(
-		tendermint_common.LeftPadWord256(key).Bytes())
-	if !exists { // value == nil {
-		return &rpc_tendermint_types.ResultGetStorage{key, nil}, nil
+		tm_common.LeftPadWord256(key).Bytes())
+	if !exists {
+		// value == nil {
+		return &rpc_tm_types.ResultGetStorage{key, nil}, nil
 	}
-	return &rpc_tendermint_types.ResultGetStorage{key, value}, nil
+	return &rpc_tm_types.ResultGetStorage{key, value}, nil
 }
 
-func (pipe *ErisMintPipe) DumpStorage(address []byte) (*rpc_tendermint_types.ResultDumpStorage,
+func (pipe *ErisMintPipe) DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage,
 	error) {
 	state := pipe.erisMint.GetState()
 	account := state.GetAccount(address)
@@ -331,17 +358,17 @@ func (pipe *ErisMintPipe) DumpStorage(address []byte) (*rpc_tendermint_types.Res
 	}
 	storageRoot := account.StorageRoot
 	storageTree := state.LoadStorage(storageRoot)
-	storageItems := []rpc_tendermint_types.StorageItem{}
+	storageItems := []rpc_tm_types.StorageItem{}
 	storageTree.Iterate(func(key []byte, value []byte) bool {
-		storageItems = append(storageItems, rpc_tendermint_types.StorageItem{key,
+		storageItems = append(storageItems, rpc_tm_types.StorageItem{key,
 			value})
 		return false
 	})
-	return &rpc_tendermint_types.ResultDumpStorage{storageRoot, storageItems}, nil
+	return &rpc_tm_types.ResultDumpStorage{storageRoot, storageItems}, nil
 }
 
 // Call
-func (pipe *ErisMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tendermint_types.ResultCall,
+func (pipe *ErisMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall,
 	error) {
 	st := pipe.erisMint.GetState()
 	cache := state.NewBlockCache(st)
@@ -350,11 +377,11 @@ func (pipe *ErisMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tender
 		return nil, fmt.Errorf("Account %x does not exist", toAddress)
 	}
 	callee := toVMAccount(outAcc)
-	caller := &vm.Account{Address: tendermint_common.LeftPadWord256(fromAddress)}
+	caller := &vm.Account{Address: tm_common.LeftPadWord256(fromAddress)}
 	txCache := state.NewTxCache(cache)
 	params := vm.Params{
 		BlockHeight: int64(st.LastBlockHeight),
-		BlockHash:   tendermint_common.LeftPadWord256(st.LastBlockHash),
+		BlockHash:   tm_common.LeftPadWord256(st.LastBlockHash),
 		BlockTime:   st.LastBlockTime.Unix(),
 		GasLimit:    st.GetGasLimit(),
 	}
@@ -365,19 +392,19 @@ func (pipe *ErisMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tender
 	if err != nil {
 		return nil, err
 	}
-	return &rpc_tendermint_types.ResultCall{Return: ret}, nil
+	return &rpc_tm_types.ResultCall{Return: ret}, nil
 }
 
-func (pipe *ErisMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tendermint_types.ResultCall,
+func (pipe *ErisMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall,
 	error) {
 	st := pipe.erisMint.GetState()
 	cache := pipe.erisMint.GetCheckCache()
-	callee := &vm.Account{Address: tendermint_common.LeftPadWord256(fromAddress)}
-	caller := &vm.Account{Address: tendermint_common.LeftPadWord256(fromAddress)}
+	callee := &vm.Account{Address: tm_common.LeftPadWord256(fromAddress)}
+	caller := &vm.Account{Address: tm_common.LeftPadWord256(fromAddress)}
 	txCache := state.NewTxCache(cache)
 	params := vm.Params{
 		BlockHeight: int64(st.LastBlockHeight),
-		BlockHash:   tendermint_common.LeftPadWord256(st.LastBlockHash),
+		BlockHash:   tm_common.LeftPadWord256(st.LastBlockHash),
 		BlockTime:   st.LastBlockTime.Unix(),
 		GasLimit:    st.GetGasLimit(),
 	}
@@ -388,14 +415,14 @@ func (pipe *ErisMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tenderm
 	if err != nil {
 		return nil, err
 	}
-	return &rpc_tendermint_types.ResultCall{Return: ret}, nil
+	return &rpc_tm_types.ResultCall{Return: ret}, nil
 }
 
 // TODO: [ben] deprecate as we should not allow unsafe behaviour
 // where a user is allowed to send a private key over the wire,
 // especially unencrypted.
 func (pipe *ErisMintPipe) SignTransaction(tx txs.Tx,
-	privAccounts []*account.PrivAccount) (*rpc_tendermint_types.ResultSignTx,
+	privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx,
 	error) {
 
 	for i, privAccount := range privAccounts {
@@ -430,20 +457,20 @@ func (pipe *ErisMintPipe) SignTransaction(tx txs.Tx,
 		rebondTx := tx.(*txs.RebondTx)
 		rebondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, rebondTx).(crypto.SignatureEd25519)
 	}
-	return &rpc_tendermint_types.ResultSignTx{tx}, nil
+	return &rpc_tm_types.ResultSignTx{tx}, nil
 }
 
 // Name registry
-func (pipe *ErisMintPipe) GetName(name string) (*rpc_tendermint_types.ResultGetName, error) {
+func (pipe *ErisMintPipe) GetName(name string) (*rpc_tm_types.ResultGetName, error) {
 	currentState := pipe.erisMint.GetState()
 	entry := currentState.GetNameRegEntry(name)
 	if entry == nil {
 		return nil, fmt.Errorf("Name %s not found", name)
 	}
-	return &rpc_tendermint_types.ResultGetName{entry}, nil
+	return &rpc_tm_types.ResultGetName{entry}, nil
 }
 
-func (pipe *ErisMintPipe) ListNames() (*rpc_tendermint_types.ResultListNames, error) {
+func (pipe *ErisMintPipe) ListNames() (*rpc_tm_types.ResultListNames, error) {
 	var blockHeight int
 	var names []*core_types.NameRegEntry
 	currentState := pipe.erisMint.GetState()
@@ -452,25 +479,27 @@ func (pipe *ErisMintPipe) ListNames() (*rpc_tendermint_types.ResultListNames, er
 		names = append(names, state.DecodeNameRegEntry(value))
 		return false
 	})
-	return &rpc_tendermint_types.ResultListNames{blockHeight, names}, nil
+	return &rpc_tm_types.ResultListNames{blockHeight, names}, nil
 }
 
 // Memory pool
 // NOTE: txs must be signed
 func (pipe *ErisMintPipe) BroadcastTxAsync(tx txs.Tx) (
-	*rpc_tendermint_types.ResultBroadcastTx, error) {
+	*rpc_tm_types.ResultBroadcastTx, error) {
 	err := pipe.consensusEngine.BroadcastTransaction(txs.EncodeTx(tx), nil)
 	if err != nil {
 		return nil, fmt.Errorf("Error broadcasting txs: %v", err)
 	}
-	return &rpc_tendermint_types.ResultBroadcastTx{}, nil
+	return &rpc_tm_types.ResultBroadcastTx{}, nil
 }
 
-func (pipe *ErisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tendermint_types.ResultBroadcastTx,
+func (pipe *ErisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx,
 	error) {
 	responseChannel := make(chan *tmsp_types.Response, 1)
 	err := pipe.consensusEngine.BroadcastTransaction(txs.EncodeTx(tx),
-		func(res *tmsp_types.Response) { responseChannel <- res })
+		func(res *tmsp_types.Response) {
+			responseChannel <- res
+		})
 	if err != nil {
 		return nil, fmt.Errorf("Error broadcasting txs: %v", err)
 	}
@@ -484,7 +513,7 @@ func (pipe *ErisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tendermint_types.Resu
 	if responseCheckTx == nil {
 		return nil, fmt.Errorf("Error, application did not return CheckTx response.")
 	}
-	resultBroadCastTx := &rpc_tendermint_types.ResultBroadcastTx{
+	resultBroadCastTx := &rpc_tm_types.ResultBroadcastTx{
 		Code: responseCheckTx.Code,
 		Data: responseCheckTx.Data,
 		Log:  responseCheckTx.Log,
@@ -505,3 +534,31 @@ func (pipe *ErisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tendermint_types.Resu
 		return resultBroadCastTx, fmt.Errorf("Unknown error returned: " + responseCheckTx.Log)
 	}
 }
+
+// Returns the current blockchain height and metadata for a range of blocks
+// between minHeight and maxHeight. Only returns maxBlockLookback block metadata
+// from the top of the range of blocks.
+// Passing 0 for maxHeight sets the upper height of the range to the current
+// blockchain height.
+func (pipe *ErisMintPipe) BlockchainInfo(minHeight, maxHeight,
+	maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) {
+
+	blockStore := pipe.blockchain.blockStore
+
+	if maxHeight < 1 {
+		maxHeight = blockStore.Height()
+	} else {
+		maxHeight = imath.MinInt(blockStore.Height(), maxHeight)
+	}
+	if minHeight < 1 {
+		minHeight = imath.MaxInt(1, maxHeight-maxBlockLookback)
+	}
+
+	blockMetas := []*tm_types.BlockMeta{}
+	for height := maxHeight; height >= minHeight; height-- {
+		blockMeta := blockStore.LoadBlockMeta(height)
+		blockMetas = append(blockMetas, blockMeta)
+	}
+
+	return &rpc_tm_types.ResultBlockchainInfo{blockStore.Height(), blockMetas}, nil
+}
diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go
index ea041d305f0e0a3b6258a2cd2c04555db6f806e2..c00d3f5579cb4d11a87935c65daafe3cb208c20c 100644
--- a/rpc/tendermint/core/routes.go
+++ b/rpc/tendermint/core/routes.go
@@ -8,59 +8,41 @@ import (
 	ctypes "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
 	"github.com/eris-ltd/eris-db/txs"
 	rpc "github.com/tendermint/go-rpc/server"
+	rpctypes "github.com/tendermint/go-rpc/types"
 )
 
 // TODO: [ben] encapsulate Routes into a struct for a given TendermintPipe
 
-// TODO: eliminate redundancy between here and reading code from core/
-// var Routes = map[string]*rpc.RPCFunc{
-// 	"status":                  rpc.NewRPCFunc(StatusResult, ""),
-// 	"net_info":                rpc.NewRPCFunc(NetInfoResult, ""),
-// 	"genesis":                 rpc.NewRPCFunc(GenesisResult, ""),
-// 	"get_account":             rpc.NewRPCFunc(GetAccountResult, "address"),
-// 	"get_storage":             rpc.NewRPCFunc(GetStorageResult, "address,key"),
-// 	"call":                    rpc.NewRPCFunc(CallResult, "fromAddress,toAddress,data"),
-// 	"call_code":               rpc.NewRPCFunc(CallCodeResult, "fromAddress,code,data"),
-// 	"dump_storage":            rpc.NewRPCFunc(DumpStorageResult, "address"),
-// 	"list_accounts":           rpc.NewRPCFunc(ListAccountsResult, ""),
-// 	"get_name":                rpc.NewRPCFunc(GetNameResult, "name"),
-// 	"list_names":              rpc.NewRPCFunc(ListNamesResult, ""),
-// 	"broadcast_tx":            rpc.NewRPCFunc(BroadcastTxResult, "tx"),
-// 	"unsafe/gen_priv_account": rpc.NewRPCFunc(GenPrivAccountResult, ""),
-// 	"unsafe/sign_tx":          rpc.NewRPCFunc(SignTxResult, "tx,privAccounts"),
-//
-// 	// TODO: hookup
-// 	//	"blockchain":              rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
-// 	//	"get_block":               rpc.NewRPCFunc(GetBlock, "height"),
-// 	//"list_validators":         rpc.NewRPCFunc(ListValidators, ""),
-// 	// "dump_consensus_state":    rpc.NewRPCFunc(DumpConsensusState, ""),
-// 	// "list_unconfirmed_txs":    rpc.NewRPCFunc(ListUnconfirmedTxs, ""),
-// 	// subscribe/unsubscribe are reserved for websocket events.
-// }
+// Magic! Should probably be configurable, but not shouldn't be so huge we
+// end up DoSing ourselves.
+const maxBlockLookback = 20
 
+// TODO: eliminate redundancy between here and reading code from core/
 type TendermintRoutes struct {
 	tendermintPipe definitions.TendermintPipe
 }
 
-func (this *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc {
+func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc {
 	var routes = map[string]*rpc.RPCFunc{
-		"status":                  rpc.NewRPCFunc(this.StatusResult, ""),
-		"net_info":                rpc.NewRPCFunc(this.NetInfoResult, ""),
-		"genesis":                 rpc.NewRPCFunc(this.GenesisResult, ""),
-		"get_account":             rpc.NewRPCFunc(this.GetAccountResult, "address"),
-		"get_storage":             rpc.NewRPCFunc(this.GetStorageResult, "address,key"),
-		"call":                    rpc.NewRPCFunc(this.CallResult, "fromAddress,toAddress,data"),
-		"call_code":               rpc.NewRPCFunc(this.CallCodeResult, "fromAddress,code,data"),
-		"dump_storage":            rpc.NewRPCFunc(this.DumpStorageResult, "address"),
-		"list_accounts":           rpc.NewRPCFunc(this.ListAccountsResult, ""),
-		"get_name":                rpc.NewRPCFunc(this.GetNameResult, "name"),
-		"list_names":              rpc.NewRPCFunc(this.ListNamesResult, ""),
-		"broadcast_tx":            rpc.NewRPCFunc(this.BroadcastTxResult, "tx"),
-		"unsafe/gen_priv_account": rpc.NewRPCFunc(this.GenPrivAccountResult, ""),
-		"unsafe/sign_tx":          rpc.NewRPCFunc(this.SignTxResult, "tx,privAccounts"),
+		"subscribe":               rpc.NewWSRPCFunc(tmRoutes.Subscribe, "event"),
+		"unsubscribe":             rpc.NewWSRPCFunc(tmRoutes.Unsubscribe, "event"),
+		"status":                  rpc.NewRPCFunc(tmRoutes.StatusResult, ""),
+		"net_info":                rpc.NewRPCFunc(tmRoutes.NetInfoResult, ""),
+		"genesis":                 rpc.NewRPCFunc(tmRoutes.GenesisResult, ""),
+		"get_account":             rpc.NewRPCFunc(tmRoutes.GetAccountResult, "address"),
+		"get_storage":             rpc.NewRPCFunc(tmRoutes.GetStorageResult, "address,key"),
+		"call":                    rpc.NewRPCFunc(tmRoutes.CallResult, "fromAddress,toAddress,data"),
+		"call_code":               rpc.NewRPCFunc(tmRoutes.CallCodeResult, "fromAddress,code,data"),
+		"dump_storage":            rpc.NewRPCFunc(tmRoutes.DumpStorageResult, "address"),
+		"list_accounts":           rpc.NewRPCFunc(tmRoutes.ListAccountsResult, ""),
+		"get_name":                rpc.NewRPCFunc(tmRoutes.GetNameResult, "name"),
+		"list_names":              rpc.NewRPCFunc(tmRoutes.ListNamesResult, ""),
+		"broadcast_tx":            rpc.NewRPCFunc(tmRoutes.BroadcastTxResult, "tx"),
+		"unsafe/gen_priv_account": rpc.NewRPCFunc(tmRoutes.GenPrivAccountResult, ""),
+		"unsafe/sign_tx":          rpc.NewRPCFunc(tmRoutes.SignTxResult, "tx,privAccounts"),
 
 		// TODO: hookup
-		//	"blockchain":              rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
+		"blockchain": rpc.NewRPCFunc(tmRoutes.BlockchainInfo, "minHeight,maxHeight"),
 		//	"get_block":               rpc.NewRPCFunc(GetBlock, "height"),
 		//"list_validators":         rpc.NewRPCFunc(ListValidators, ""),
 		// "dump_consensus_state":    rpc.NewRPCFunc(DumpConsensusState, ""),
@@ -70,96 +52,125 @@ func (this *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc {
 	return routes
 }
 
-func (this *TendermintRoutes) StatusResult() (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.Status(); err != nil {
+func (tmRoutes *TendermintRoutes) Subscribe(wsCtx rpctypes.WSRPCContext,
+	event string) (ctypes.ErisDBResult, error) {
+	// NOTE: RPCResponses of subscribed events have id suffix "#event"
+	result, err := tmRoutes.tendermintPipe.Subscribe(wsCtx.GetRemoteAddr(), event,
+		func(result ctypes.ErisDBResult) {
+			// NOTE: EventSwitch callbacks must be nonblocking
+			wsCtx.TryWriteRPCResponse(
+				rpctypes.NewRPCResponse(wsCtx.Request.ID+"#event", &result, ""))
+		})
+	if err != nil {
+		return nil, err
+	} else {
+		return result, nil
+	}
+}
+
+func (tmRoutes *TendermintRoutes) Unsubscribe(wsCtx rpctypes.WSRPCContext,
+	event string) (ctypes.ErisDBResult, error) {
+	result, err := tmRoutes.tendermintPipe.Unsubscribe(wsCtx.GetRemoteAddr(),
+		event)
+	if err != nil {
+		return nil, err
+	} else {
+		return result, nil
+	}
+}
+
+func (tmRoutes *TendermintRoutes) StatusResult() (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.Status(); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) NetInfoResult() (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.NetInfo(); err != nil {
+func (tmRoutes *TendermintRoutes) NetInfoResult() (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.NetInfo(); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) GenesisResult() (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.Genesis(); err != nil {
+func (tmRoutes *TendermintRoutes) GenesisResult() (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.Genesis(); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) GetAccountResult(address []byte) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.GetAccount(address); err != nil {
+func (tmRoutes *TendermintRoutes) GetAccountResult(address []byte) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.GetAccount(address); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) GetStorageResult(address, key []byte) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.GetStorage(address, key); err != nil {
+func (tmRoutes *TendermintRoutes) GetStorageResult(address, key []byte) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.GetStorage(address, key); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) CallResult(fromAddress, toAddress, data []byte) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.Call(fromAddress, toAddress, data); err != nil {
+func (tmRoutes *TendermintRoutes) CallResult(fromAddress, toAddress,
+	data []byte) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.Call(fromAddress, toAddress, data); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) CallCodeResult(fromAddress, code, data []byte) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.CallCode(fromAddress, code, data); err != nil {
+func (tmRoutes *TendermintRoutes) CallCodeResult(fromAddress, code,
+	data []byte) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.CallCode(fromAddress, code, data); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) DumpStorageResult(address []byte) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.DumpStorage(address); err != nil {
+func (tmRoutes *TendermintRoutes) DumpStorageResult(address []byte) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.DumpStorage(address); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) ListAccountsResult() (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.ListAccounts(); err != nil {
+func (tmRoutes *TendermintRoutes) ListAccountsResult() (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.ListAccounts(); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) GetNameResult(name string) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.GetName(name); err != nil {
+func (tmRoutes *TendermintRoutes) GetNameResult(name string) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.GetName(name); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) ListNamesResult() (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.ListNames(); err != nil {
+func (tmRoutes *TendermintRoutes) ListNamesResult() (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.ListNames(); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
 
-func (this *TendermintRoutes) GenPrivAccountResult() (ctypes.ErisDBResult, error) {
-	//if r, err := this.tendermintPipe.GenPrivAccount(); err != nil {
+func (tmRoutes *TendermintRoutes) GenPrivAccountResult() (ctypes.ErisDBResult, error) {
+	//if r, err := tmRoutes.tendermintPipe.GenPrivAccount(); err != nil {
 	//	return nil, err
 	//} else {
 	//	return r, nil
@@ -167,8 +178,9 @@ func (this *TendermintRoutes) GenPrivAccountResult() (ctypes.ErisDBResult, error
 	return nil, fmt.Errorf("Unimplemented as poor practice to generate private account over unencrypted RPC")
 }
 
-func (this *TendermintRoutes) SignTxResult(tx txs.Tx, privAccounts []*acm.PrivAccount) (ctypes.ErisDBResult, error) {
-	// if r, err := this.tendermintPipe.SignTx(tx, privAccounts); err != nil {
+func (tmRoutes *TendermintRoutes) SignTxResult(tx txs.Tx,
+	privAccounts []*acm.PrivAccount) (ctypes.ErisDBResult, error) {
+	// if r, err := tmRoutes.tendermintPipe.SignTx(tx, privAccounts); err != nil {
 	// 	return nil, err
 	// } else {
 	// 	return r, nil
@@ -176,10 +188,22 @@ func (this *TendermintRoutes) SignTxResult(tx txs.Tx, privAccounts []*acm.PrivAc
 	return nil, fmt.Errorf("Unimplemented as poor practice to pass private account over unencrypted RPC")
 }
 
-func (this *TendermintRoutes) BroadcastTxResult(tx txs.Tx) (ctypes.ErisDBResult, error) {
-	if r, err := this.tendermintPipe.BroadcastTxSync(tx); err != nil {
+func (tmRoutes *TendermintRoutes) BroadcastTxResult(tx txs.Tx) (ctypes.ErisDBResult, error) {
+	if r, err := tmRoutes.tendermintPipe.BroadcastTxSync(tx); err != nil {
 		return nil, err
 	} else {
 		return r, nil
 	}
 }
+
+func (tmRoutes *TendermintRoutes) BlockchainInfo(minHeight,
+	maxHeight int) (ctypes.ErisDBResult, error) {
+	r, err := tmRoutes.tendermintPipe.BlockchainInfo(minHeight, maxHeight,
+		maxBlockLookback)
+	if err != nil {
+		return nil, err
+	} else {
+		return r, nil
+	}
+
+}
diff --git a/rpc/tendermint/core/types/responses.go b/rpc/tendermint/core/types/responses.go
index 4c48f7ab7a5410ef1dc6cb488218fa54e88895f8..77ab79af0b4a7063c7fb3c108b03cfde3de6d164 100644
--- a/rpc/tendermint/core/types/responses.go
+++ b/rpc/tendermint/core/types/responses.go
@@ -59,6 +59,12 @@ type ResultStatus struct {
 	LatestBlockTime   int64         `json:"latest_block_time"` // nano
 }
 
+type ResultSubscribe struct {
+}
+
+type ResultUnsubscribe struct {
+}
+
 type ResultNetInfo struct {
 	Listening bool     `json:"listening"`
 	Listeners []string `json:"listeners"`
@@ -145,6 +151,8 @@ const (
 	ResultTypeGenesis            = byte(0x11)
 	ResultTypeSignTx             = byte(0x12)
 	ResultTypeEvent              = byte(0x13) // so websockets can respond to rpc functions
+	ResultTypeSubscribe          = byte(0x14)
+	ResultTypeUnsubscribe        = byte(0x15)
 )
 
 type ErisDBResult interface {
@@ -173,4 +181,6 @@ var _ = wire.RegisterInterface(
 	wire.ConcreteType{&ResultGenesis{}, ResultTypeGenesis},
 	wire.ConcreteType{&ResultSignTx{}, ResultTypeSignTx},
 	wire.ConcreteType{&ResultEvent{}, ResultTypeEvent},
+	wire.ConcreteType{&ResultSubscribe{}, ResultTypeSubscribe},
+	wire.ConcreteType{&ResultUnsubscribe{}, ResultTypeUnsubscribe},
 )