From 1ebdd15f3febc20639003f62f53b154879dfa68b Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Mon, 23 Jul 2018 15:38:37 +0100
Subject: [PATCH] Unify Status with LastBlockInfo, removing the later and add
 Status call to QueryServer for use by both and others. Status now serves as
 healthcheck with blockWithin parameter

Signed-off-by: Silas Davis <silas@monax.io>
---
 acm/validator/validator.pb.go             |  29 +-
 consensus/tendermint/node_view.go         |  29 +-
 consensus/tendermint/tendermint.go        |  21 +-
 consensus/tendermint/tendermint.pb.go     | 609 ++++++++++++++++++++
 core/kernel.go                            |   5 +-
 integration/rpcquery/query_server_test.go |  19 +
 protobuf/rpc.proto                        |  31 +
 protobuf/rpcquery.proto                   |   8 +
 protobuf/tendermint.proto                 |  25 +
 rpc/metrics/export.go                     |   2 +-
 rpc/metrics/server.go                     |   2 +-
 rpc/result.go                             |  24 +-
 rpc/result_test.go                        |  17 -
 rpc/rpc.pb.go                             | 652 ++++++++++++++++++++++
 rpc/rpcquery/query_server.go              |   5 +
 rpc/rpcquery/rpcquery.pb.go               | 251 +++++++--
 rpc/service.go                            |  50 +-
 rpc/tm/methods.go                         |   8 +-
 18 files changed, 1631 insertions(+), 156 deletions(-)
 create mode 100644 consensus/tendermint/tendermint.pb.go
 create mode 100644 protobuf/rpc.proto
 create mode 100644 protobuf/tendermint.proto
 create mode 100644 rpc/rpc.pb.go

diff --git a/acm/validator/validator.pb.go b/acm/validator/validator.pb.go
index 93c8b77d..b15639c0 100644
--- a/acm/validator/validator.pb.go
+++ b/acm/validator/validator.pb.go
@@ -17,7 +17,6 @@ import golang_proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
 import math "math"
 import _ "github.com/gogo/protobuf/gogoproto"
-import _ "github.com/hyperledger/burrow/permission"
 import crypto "github.com/hyperledger/burrow/crypto"
 
 import github_com_hyperledger_burrow_crypto "github.com/hyperledger/burrow/crypto"
@@ -385,21 +384,21 @@ func init() { proto.RegisterFile("validator.proto", fileDescriptorValidator) }
 func init() { golang_proto.RegisterFile("validator.proto", fileDescriptorValidator) }
 
 var fileDescriptorValidator = []byte{
-	// 255 bytes of a gzipped FileDescriptorProto
+	// 242 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x4b, 0xcc, 0xc9,
 	0x4c, 0x49, 0x2c, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x48,
 	0xe9, 0xa6, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa7, 0xe7, 0xa7, 0xe7,
-	0xeb, 0x83, 0x55, 0x24, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0xd1, 0x29, 0x25, 0x50,
-	0x90, 0x5a, 0x94, 0x9b, 0x59, 0x5c, 0x9c, 0x99, 0x9f, 0x07, 0x15, 0xe1, 0x49, 0x2e, 0xaa, 0x2c,
-	0x28, 0x81, 0xca, 0x2b, 0xad, 0x62, 0xe4, 0xe2, 0x0c, 0x83, 0x19, 0x2e, 0xe4, 0xc5, 0xc5, 0xee,
-	0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, 0x2c, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe3, 0x64, 0x70, 0xeb,
-	0x9e, 0xbc, 0x0e, 0x92, 0x8d, 0x19, 0x95, 0x05, 0xa9, 0x45, 0x39, 0xa9, 0x29, 0xe9, 0xa9, 0x45,
-	0xfa, 0x49, 0xa5, 0x45, 0x45, 0xf9, 0xe5, 0xfa, 0x50, 0xe3, 0xa0, 0xfa, 0x82, 0x60, 0x06, 0x08,
-	0x99, 0x72, 0x71, 0x06, 0x94, 0x26, 0xe5, 0x64, 0x26, 0x7b, 0xa7, 0x56, 0x4a, 0x30, 0x29, 0x30,
-	0x6a, 0x70, 0x1b, 0x09, 0xea, 0x41, 0x15, 0xc3, 0x25, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08,
-	0x42, 0xa8, 0x14, 0x12, 0xe1, 0x62, 0x0d, 0xc8, 0x2f, 0x4f, 0x2d, 0x92, 0x60, 0x56, 0x60, 0xd4,
-	0x60, 0x09, 0x82, 0x70, 0xac, 0x58, 0x66, 0x2c, 0x90, 0x67, 0x70, 0x72, 0x3c, 0xf1, 0x48, 0x8e,
-	0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x0f, 0x3c, 0x96, 0x63, 0x3c, 0xf1, 0x58,
-	0x8e, 0x31, 0x4a, 0x1b, 0xbf, 0xfb, 0x12, 0x93, 0x73, 0xf5, 0xe1, 0xc1, 0x97, 0xc4, 0x06, 0xf6,
-	0xb6, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x1c, 0xca, 0xd9, 0x6f, 0x63, 0x01, 0x00, 0x00,
+	0xeb, 0x83, 0x55, 0x24, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0xd1, 0x29, 0xc5, 0x93,
+	0x5c, 0x54, 0x59, 0x50, 0x02, 0xe5, 0x29, 0xad, 0x62, 0xe4, 0xe2, 0x0c, 0x83, 0x19, 0x25, 0xe4,
+	0xc5, 0xc5, 0xee, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, 0x2c, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe3,
+	0x64, 0x70, 0xeb, 0x9e, 0xbc, 0x0e, 0x92, 0xf9, 0x19, 0x95, 0x05, 0xa9, 0x45, 0x39, 0xa9, 0x29,
+	0xe9, 0xa9, 0x45, 0xfa, 0x49, 0xa5, 0x45, 0x45, 0xf9, 0xe5, 0xfa, 0x50, 0xe3, 0xa0, 0xfa, 0x82,
+	0x60, 0x06, 0x08, 0x99, 0x72, 0x71, 0x06, 0x94, 0x26, 0xe5, 0x64, 0x26, 0x7b, 0xa7, 0x56, 0x4a,
+	0x30, 0x29, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xea, 0x41, 0x15, 0xc3, 0x25, 0x9c, 0x58, 0x4e, 0xdc,
+	0x93, 0x67, 0x08, 0x42, 0xa8, 0x14, 0x12, 0xe1, 0x62, 0x0d, 0xc8, 0x2f, 0x4f, 0x2d, 0x92, 0x60,
+	0x56, 0x60, 0xd4, 0x60, 0x09, 0x82, 0x70, 0xac, 0x58, 0x66, 0x2c, 0x90, 0x67, 0x70, 0x72, 0x3c,
+	0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x0f, 0x3c, 0x96, 0x63,
+	0x3c, 0xf1, 0x58, 0x8e, 0x31, 0x4a, 0x1b, 0xbf, 0xfb, 0x12, 0x93, 0x73, 0xf5, 0xe1, 0x81, 0x95,
+	0xc4, 0x06, 0xf6, 0xb6, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xe5, 0x4a, 0x65, 0x51, 0x01,
+	0x00, 0x00,
 }
diff --git a/consensus/tendermint/node_view.go b/consensus/tendermint/node_view.go
index 62a50085..023533e1 100644
--- a/consensus/tendermint/node_view.go
+++ b/consensus/tendermint/node_view.go
@@ -7,32 +7,35 @@ import (
 	"github.com/hyperledger/burrow/txs"
 	"github.com/tendermint/tendermint/consensus"
 	ctypes "github.com/tendermint/tendermint/consensus/types"
-	tmCrypto "github.com/tendermint/tendermint/crypto"
 	"github.com/tendermint/tendermint/p2p"
 	"github.com/tendermint/tendermint/state"
 	"github.com/tendermint/tendermint/types"
 )
 
 type NodeView struct {
-	tmNode    *Node
-	txDecoder txs.Decoder
+	tmNode             *Node
+	validatorPublicKey crypto.PublicKey
+	txDecoder          txs.Decoder
 }
 
-func NewNodeView(tmNode *Node, txDecoder txs.Decoder) *NodeView {
-	return &NodeView{
-		tmNode:    tmNode,
-		txDecoder: txDecoder,
+func NewNodeView(tmNode *Node, txDecoder txs.Decoder) (*NodeView, error) {
+	publicKey, err := crypto.PublicKeyFromTendermintPubKey(tmNode.PrivValidator().GetPubKey())
+	if err != nil {
+		return nil, err
 	}
+	return &NodeView{
+		validatorPublicKey: publicKey,
+		tmNode:             tmNode,
+		txDecoder:          txDecoder,
+	}, nil
 }
 
-func (nv *NodeView) PrivValidatorPublicKey() (crypto.PublicKey, error) {
-	pub := nv.tmNode.PrivValidator().GetPubKey().(tmCrypto.PubKeyEd25519)
-
-	return crypto.PublicKeyFromBytes(pub[:], crypto.CurveTypeEd25519)
+func (nv *NodeView) ValidatorPublicKey() crypto.PublicKey {
+	return nv.validatorPublicKey
 }
 
-func (nv *NodeView) NodeInfo() p2p.NodeInfo {
-	return nv.tmNode.NodeInfo()
+func (nv *NodeView) NodeInfo() *NodeInfo {
+	return NewNodeInfo(nv.tmNode.NodeInfo())
 }
 
 func (nv *NodeView) IsListening() bool {
diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go
index 0122f580..573265ee 100644
--- a/consensus/tendermint/tendermint.go
+++ b/consensus/tendermint/tendermint.go
@@ -4,7 +4,9 @@ import (
 	"os"
 	"path"
 
+	"github.com/hyperledger/burrow/binary"
 	"github.com/hyperledger/burrow/consensus/tendermint/abci"
+	"github.com/hyperledger/burrow/crypto"
 	"github.com/hyperledger/burrow/genesis"
 	"github.com/hyperledger/burrow/logging"
 	"github.com/hyperledger/burrow/logging/structure"
@@ -12,6 +14,7 @@ import (
 	tmCrypto "github.com/tendermint/tendermint/crypto"
 	dbm "github.com/tendermint/tendermint/libs/db"
 	"github.com/tendermint/tendermint/node"
+	"github.com/tendermint/tendermint/p2p"
 	"github.com/tendermint/tendermint/proxy"
 	tmTypes "github.com/tendermint/tendermint/types"
 )
@@ -90,13 +93,15 @@ func DeriveGenesisDoc(burrowGenesisDoc *genesis.GenesisDoc) *tmTypes.GenesisDoc
 	}
 }
 
-func NewBlockEvent(message interface{}) *tmTypes.EventDataNewBlock {
-	tmEventData, ok := message.(tmTypes.TMEventData)
-	if ok {
-		eventDataNewBlock, ok := tmEventData.(tmTypes.EventDataNewBlock)
-		if ok {
-			return &eventDataNewBlock
-		}
+func NewNodeInfo(ni p2p.NodeInfo) *NodeInfo {
+	address, _ := crypto.AddressFromHexString(string(ni.ID))
+	return &NodeInfo{
+		ID:            address,
+		Moniker:       ni.Moniker,
+		ListenAddress: ni.ListenAddr,
+		Version:       ni.Version,
+		Channels:      binary.HexBytes(ni.Channels),
+		Network:       ni.Network,
+		Other:         ni.Other,
 	}
-	return nil
 }
diff --git a/consensus/tendermint/tendermint.pb.go b/consensus/tendermint/tendermint.pb.go
new file mode 100644
index 00000000..6dd3e449
--- /dev/null
+++ b/consensus/tendermint/tendermint.pb.go
@@ -0,0 +1,609 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: tendermint.proto
+
+/*
+	Package tendermint is a generated protocol buffer package.
+
+	It is generated from these files:
+		tendermint.proto
+
+	It has these top-level messages:
+		NodeInfo
+*/
+package tendermint
+
+import proto "github.com/gogo/protobuf/proto"
+import golang_proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+import _ "github.com/hyperledger/burrow/crypto"
+
+import github_com_hyperledger_burrow_crypto "github.com/hyperledger/burrow/crypto"
+import github_com_hyperledger_burrow_binary "github.com/hyperledger/burrow/binary"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = golang_proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+type NodeInfo struct {
+	ID            github_com_hyperledger_burrow_crypto.Address  `protobuf:"bytes,1,opt,name=ID,proto3,customtype=github.com/hyperledger/burrow/crypto.Address" json:"ID"`
+	ListenAddress string                                        `protobuf:"bytes,2,opt,name=ListenAddress,proto3" json:"ListenAddress,omitempty"`
+	Network       string                                        `protobuf:"bytes,3,opt,name=Network,proto3" json:"Network,omitempty"`
+	Version       string                                        `protobuf:"bytes,4,opt,name=Version,proto3" json:"Version,omitempty"`
+	Channels      github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,5,opt,name=Channels,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"Channels"`
+	Moniker       string                                        `protobuf:"bytes,6,opt,name=Moniker,proto3" json:"Moniker,omitempty"`
+	Other         []string                                      `protobuf:"bytes,7,rep,name=Other" json:"Other,omitempty"`
+}
+
+func (m *NodeInfo) Reset()                    { *m = NodeInfo{} }
+func (m *NodeInfo) String() string            { return proto.CompactTextString(m) }
+func (*NodeInfo) ProtoMessage()               {}
+func (*NodeInfo) Descriptor() ([]byte, []int) { return fileDescriptorTendermint, []int{0} }
+
+func (m *NodeInfo) GetListenAddress() string {
+	if m != nil {
+		return m.ListenAddress
+	}
+	return ""
+}
+
+func (m *NodeInfo) GetNetwork() string {
+	if m != nil {
+		return m.Network
+	}
+	return ""
+}
+
+func (m *NodeInfo) GetVersion() string {
+	if m != nil {
+		return m.Version
+	}
+	return ""
+}
+
+func (m *NodeInfo) GetMoniker() string {
+	if m != nil {
+		return m.Moniker
+	}
+	return ""
+}
+
+func (m *NodeInfo) GetOther() []string {
+	if m != nil {
+		return m.Other
+	}
+	return nil
+}
+
+func (*NodeInfo) XXX_MessageName() string {
+	return "tendermint.NodeInfo"
+}
+func init() {
+	proto.RegisterType((*NodeInfo)(nil), "tendermint.NodeInfo")
+	golang_proto.RegisterType((*NodeInfo)(nil), "tendermint.NodeInfo")
+}
+func (m *NodeInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NodeInfo) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	dAtA[i] = 0xa
+	i++
+	i = encodeVarintTendermint(dAtA, i, uint64(m.ID.Size()))
+	n1, err := m.ID.MarshalTo(dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n1
+	if len(m.ListenAddress) > 0 {
+		dAtA[i] = 0x12
+		i++
+		i = encodeVarintTendermint(dAtA, i, uint64(len(m.ListenAddress)))
+		i += copy(dAtA[i:], m.ListenAddress)
+	}
+	if len(m.Network) > 0 {
+		dAtA[i] = 0x1a
+		i++
+		i = encodeVarintTendermint(dAtA, i, uint64(len(m.Network)))
+		i += copy(dAtA[i:], m.Network)
+	}
+	if len(m.Version) > 0 {
+		dAtA[i] = 0x22
+		i++
+		i = encodeVarintTendermint(dAtA, i, uint64(len(m.Version)))
+		i += copy(dAtA[i:], m.Version)
+	}
+	dAtA[i] = 0x2a
+	i++
+	i = encodeVarintTendermint(dAtA, i, uint64(m.Channels.Size()))
+	n2, err := m.Channels.MarshalTo(dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n2
+	if len(m.Moniker) > 0 {
+		dAtA[i] = 0x32
+		i++
+		i = encodeVarintTendermint(dAtA, i, uint64(len(m.Moniker)))
+		i += copy(dAtA[i:], m.Moniker)
+	}
+	if len(m.Other) > 0 {
+		for _, s := range m.Other {
+			dAtA[i] = 0x3a
+			i++
+			l = len(s)
+			for l >= 1<<7 {
+				dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
+				l >>= 7
+				i++
+			}
+			dAtA[i] = uint8(l)
+			i++
+			i += copy(dAtA[i:], s)
+		}
+	}
+	return i, nil
+}
+
+func encodeVarintTendermint(dAtA []byte, offset int, v uint64) int {
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return offset + 1
+}
+func (m *NodeInfo) Size() (n int) {
+	var l int
+	_ = l
+	l = m.ID.Size()
+	n += 1 + l + sovTendermint(uint64(l))
+	l = len(m.ListenAddress)
+	if l > 0 {
+		n += 1 + l + sovTendermint(uint64(l))
+	}
+	l = len(m.Network)
+	if l > 0 {
+		n += 1 + l + sovTendermint(uint64(l))
+	}
+	l = len(m.Version)
+	if l > 0 {
+		n += 1 + l + sovTendermint(uint64(l))
+	}
+	l = m.Channels.Size()
+	n += 1 + l + sovTendermint(uint64(l))
+	l = len(m.Moniker)
+	if l > 0 {
+		n += 1 + l + sovTendermint(uint64(l))
+	}
+	if len(m.Other) > 0 {
+		for _, s := range m.Other {
+			l = len(s)
+			n += 1 + l + sovTendermint(uint64(l))
+		}
+	}
+	return n
+}
+
+func sovTendermint(x uint64) (n int) {
+	for {
+		n++
+		x >>= 7
+		if x == 0 {
+			break
+		}
+	}
+	return n
+}
+func sozTendermint(x uint64) (n int) {
+	return sovTendermint(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *NodeInfo) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowTendermint
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NodeInfo: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NodeInfo: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ID.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ListenAddress", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ListenAddress = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Network", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Network = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Version = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Channels.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 6:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Moniker", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Moniker = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 7:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Other", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Other = append(m.Other, string(dAtA[iNdEx:postIndex]))
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipTendermint(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthTendermint
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipTendermint(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowTendermint
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+			return iNdEx, nil
+		case 1:
+			iNdEx += 8
+			return iNdEx, nil
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowTendermint
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			iNdEx += length
+			if length < 0 {
+				return 0, ErrInvalidLengthTendermint
+			}
+			return iNdEx, nil
+		case 3:
+			for {
+				var innerWire uint64
+				var start int = iNdEx
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return 0, ErrIntOverflowTendermint
+					}
+					if iNdEx >= l {
+						return 0, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					innerWire |= (uint64(b) & 0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				innerWireType := int(innerWire & 0x7)
+				if innerWireType == 4 {
+					break
+				}
+				next, err := skipTendermint(dAtA[start:])
+				if err != nil {
+					return 0, err
+				}
+				iNdEx = start + next
+			}
+			return iNdEx, nil
+		case 4:
+			return iNdEx, nil
+		case 5:
+			iNdEx += 4
+			return iNdEx, nil
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+	}
+	panic("unreachable")
+}
+
+var (
+	ErrInvalidLengthTendermint = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowTendermint   = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("tendermint.proto", fileDescriptorTendermint) }
+func init() { golang_proto.RegisterFile("tendermint.proto", fileDescriptorTendermint) }
+
+var fileDescriptorTendermint = []byte{
+	// 321 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xbd, 0x4e, 0x3a, 0x41,
+	0x14, 0xc5, 0xff, 0xb3, 0xfc, 0xf9, 0x9a, 0x60, 0x62, 0x36, 0x16, 0x13, 0x8a, 0x85, 0x18, 0x0b,
+	0x0a, 0x61, 0x13, 0x3f, 0x1e, 0x40, 0xa4, 0x80, 0x44, 0x31, 0x6e, 0x61, 0x61, 0xc7, 0xb2, 0x97,
+	0xdd, 0x0d, 0x30, 0x97, 0xdc, 0x99, 0x0d, 0xee, 0x43, 0xf9, 0x0e, 0x96, 0x94, 0xd6, 0x16, 0xc4,
+	0xc0, 0x8b, 0x18, 0x66, 0x57, 0xd1, 0x46, 0xbb, 0xf9, 0x9d, 0x33, 0x73, 0xee, 0xc9, 0x5c, 0x7e,
+	0xa8, 0x41, 0x06, 0x40, 0xf3, 0x58, 0xea, 0xce, 0x82, 0x50, 0xa3, 0xcd, 0xf7, 0x4a, 0xbd, 0x1d,
+	0xc6, 0x3a, 0x4a, 0xfc, 0xce, 0x18, 0xe7, 0x6e, 0x88, 0x21, 0xba, 0xe6, 0x8a, 0x9f, 0x4c, 0x0c,
+	0x19, 0x30, 0xa7, 0xec, 0x69, 0xbd, 0x36, 0xa6, 0x74, 0xa1, 0x73, 0x3a, 0x7e, 0xb6, 0x78, 0x65,
+	0x88, 0x01, 0x0c, 0xe4, 0x04, 0xed, 0x1e, 0xb7, 0x06, 0x3d, 0xc1, 0x9a, 0xac, 0x55, 0xeb, 0x5e,
+	0xac, 0xd6, 0x8d, 0x7f, 0x6f, 0xeb, 0xc6, 0xe9, 0xb7, 0xf4, 0x28, 0x5d, 0x00, 0xcd, 0x20, 0x08,
+	0x81, 0x5c, 0x3f, 0x21, 0xc2, 0xa5, 0x9b, 0x87, 0x5d, 0x05, 0x01, 0x81, 0x52, 0x9e, 0x35, 0xe8,
+	0xd9, 0x27, 0xfc, 0xe0, 0x26, 0x56, 0x1a, 0x64, 0x2e, 0x0a, 0xab, 0xc9, 0x5a, 0x55, 0xef, 0xa7,
+	0x68, 0x0b, 0x5e, 0x1e, 0x82, 0x5e, 0x22, 0x4d, 0x45, 0xc1, 0xf8, 0x9f, 0xb8, 0x73, 0x1e, 0x80,
+	0x54, 0x8c, 0x52, 0xfc, 0xcf, 0x9c, 0x1c, 0xed, 0x7b, 0x5e, 0xb9, 0x8e, 0x46, 0x52, 0xc2, 0x4c,
+	0x89, 0xa2, 0x69, 0x79, 0x99, 0xb7, 0x6c, 0xff, 0xde, 0xd2, 0x8f, 0xe5, 0x88, 0xd2, 0x4e, 0x1f,
+	0x9e, 0xba, 0xa9, 0x06, 0xe5, 0x7d, 0xc5, 0xec, 0x86, 0xdd, 0xa2, 0x8c, 0xa7, 0x40, 0xa2, 0x94,
+	0x0d, 0xcb, 0xd1, 0x3e, 0xe2, 0xc5, 0x3b, 0x1d, 0x01, 0x89, 0x72, 0xb3, 0xd0, 0xaa, 0x7a, 0x19,
+	0x74, 0xfb, 0xab, 0x8d, 0xc3, 0x5e, 0x37, 0x0e, 0x7b, 0xdf, 0x38, 0xec, 0x65, 0xeb, 0xb0, 0xd5,
+	0xd6, 0x61, 0x8f, 0x67, 0x7f, 0x7c, 0x12, 0x4a, 0x05, 0x52, 0x25, 0xca, 0xdd, 0xaf, 0xcd, 0x2f,
+	0x99, 0x05, 0x9c, 0x7f, 0x04, 0x00, 0x00, 0xff, 0xff, 0x29, 0x50, 0xa8, 0x1b, 0xdd, 0x01, 0x00,
+	0x00,
+}
diff --git a/core/kernel.go b/core/kernel.go
index 46854844..28ff55a7 100644
--- a/core/kernel.go
+++ b/core/kernel.go
@@ -126,7 +126,10 @@ func NewKernel(ctx context.Context, keyClient keys.KeyClient, privValidator tmTy
 
 	nameRegState := kern.State
 	accountState := kern.State
-	nodeView := tendermint.NewNodeView(kern.Node, txCodec)
+	nodeView, err := tendermint.NewNodeView(kern.Node, txCodec)
+	if err != nil {
+		return nil, err
+	}
 	kern.Service = rpc.NewService(accountState, nameRegState, kern.Blockchain, nodeView, kern.Logger)
 
 	kern.Launchers = []process.Launcher{
diff --git a/integration/rpcquery/query_server_test.go b/integration/rpcquery/query_server_test.go
index 05bb7e85..adef77c7 100644
--- a/integration/rpcquery/query_server_test.go
+++ b/integration/rpcquery/query_server_test.go
@@ -18,6 +18,25 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestStatus(t *testing.T) {
+	cli := rpctest.NewQueryClient(t, testConfig.RPC.GRPC.ListenAddress)
+	stat, err := cli.Status(context.Background(), &rpcquery.StatusParam{})
+	require.NoError(t, err)
+	assert.Equal(t, rpctest.PrivateAccounts[0].PublicKey(), stat.PublicKey)
+	assert.Equal(t, rpctest.GenesisDoc.ChainID(), stat.ChainID)
+	for i := 0; i < 3; i++ {
+		// Unless we get lucky this is an error
+		_, err = cli.Status(context.Background(), &rpcquery.StatusParam{
+			BlockWithin: "1ns",
+		})
+		if err != nil {
+			break
+		}
+	}
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "no block committed within")
+}
+
 func TestGetAccount(t *testing.T) {
 	cli := rpctest.NewQueryClient(t, testConfig.RPC.GRPC.ListenAddress)
 	ca, err := cli.GetAccount(context.Background(), &rpcquery.GetAccountParam{
diff --git a/protobuf/rpc.proto b/protobuf/rpc.proto
new file mode 100644
index 00000000..88f78933
--- /dev/null
+++ b/protobuf/rpc.proto
@@ -0,0 +1,31 @@
+// Needed to proto2 rather than proto3 to get pointer field for PermArg
+syntax = 'proto3';
+
+option go_package = "github.com/hyperledger/burrow/rpc";
+
+import "github.com/gogo/protobuf/gogoproto/gogo.proto";
+import "crypto.proto";
+import "tendermint.proto";
+import "google/protobuf/timestamp.proto";
+
+package rpc;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_registration) = true;
+option (gogoproto.messagename_all) = true;
+
+message ResultStatus {
+    string ChainID = 1;
+    bytes GenesisHash = 4 [(gogoproto.customtype) = "github.com/hyperledger/burrow/binary.HexBytes", (gogoproto.nullable) = false];
+    tendermint.NodeInfo NodeInfo = 2;
+    string NodeVersion = 3;
+    crypto.PublicKey PublicKey = 5 [(gogoproto.nullable) = false];
+    bytes LatestBlockHash = 6 [(gogoproto.customtype) = "github.com/hyperledger/burrow/binary.HexBytes", (gogoproto.nullable) = false];
+    uint64 LatestBlockHeight = 7;
+    // Unix
+
+    google.protobuf.Timestamp LatestBlockTime = 8 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
+}
+
diff --git a/protobuf/rpcquery.proto b/protobuf/rpcquery.proto
index fc8b6626..8981b7b7 100644
--- a/protobuf/rpcquery.proto
+++ b/protobuf/rpcquery.proto
@@ -9,6 +9,7 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto";
 import "names.proto";
 import "acm.proto";
 import "validator.proto";
+import "rpc.proto";
 
 option (gogoproto.marshaler_all) = true;
 option (gogoproto.unmarshaler_all) = true;
@@ -17,13 +18,20 @@ option (gogoproto.goproto_registration) = true;
 option (gogoproto.messagename_all) = true;
 
 service Query {
+    rpc Status (StatusParam) returns (rpc.ResultStatus);
     rpc GetAccount (GetAccountParam) returns (acm.ConcreteAccount);
     rpc ListAccounts (ListAccountsParam) returns (stream acm.ConcreteAccount);
+
     rpc GetName (GetNameParam) returns (names.Entry);
     rpc ListNames (ListNamesParam) returns (stream names.Entry);
+
     rpc GetValidatorSet (GetValidatorSetParam) returns (ValidatorSet);
 }
 
+message StatusParam {
+    string BlockWithin = 1;
+}
+
 message GetAccountParam {
     bytes Address = 1 [(gogoproto.customtype) = "github.com/hyperledger/burrow/crypto.Address", (gogoproto.nullable) = false];
 }
diff --git a/protobuf/tendermint.proto b/protobuf/tendermint.proto
new file mode 100644
index 00000000..cef3f0c3
--- /dev/null
+++ b/protobuf/tendermint.proto
@@ -0,0 +1,25 @@
+// Needed to proto2 rather than proto3 to get pointer field for PermArg
+syntax = 'proto3';
+
+option go_package = "github.com/hyperledger/burrow/consensus/tendermint";
+
+import "github.com/gogo/protobuf/gogoproto/gogo.proto";
+import "crypto.proto";
+
+package tendermint;
+
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.goproto_registration) = true;
+option (gogoproto.messagename_all) = true;
+
+message NodeInfo {
+    bytes ID = 1 [(gogoproto.customtype) = "github.com/hyperledger/burrow/crypto.Address", (gogoproto.nullable) = false];
+    string ListenAddress = 2;
+    string Network = 3;
+    string Version = 4;
+    bytes Channels = 5 [(gogoproto.customtype) = "github.com/hyperledger/burrow/binary.HexBytes", (gogoproto.nullable) = false];
+    string Moniker = 6;
+    repeated string Other = 7;
+}
diff --git a/rpc/metrics/export.go b/rpc/metrics/export.go
index 51e105c9..d64a8577 100644
--- a/rpc/metrics/export.go
+++ b/rpc/metrics/export.go
@@ -53,7 +53,7 @@ func (e *Exporter) gatherData() error {
 
 // Get status
 func (e *Exporter) getStatus() error {
-	res, err := e.service.Status()
+	res, err := e.service.Status("")
 	if err != nil {
 		return err
 	}
diff --git a/rpc/metrics/server.go b/rpc/metrics/server.go
index 5eab0754..273996dd 100644
--- a/rpc/metrics/server.go
+++ b/rpc/metrics/server.go
@@ -55,7 +55,7 @@ func StartServer(service *rpc.Service, pattern, listenAddress string, blockSampl
 	logger *logging.Logger) (*http.Server, error) {
 
 	// instantiate metrics and variables we do not expect to change during runtime
-	chainStatus, _ := service.Status()
+	chainStatus, _ := service.Status("")
 	exporter := Exporter{
 		burrowMetrics:    AddMetrics(),
 		datum:            &Datum{},
diff --git a/rpc/result.go b/rpc/result.go
index 35d21892..a7ca72ed 100644
--- a/rpc/result.go
+++ b/rpc/result.go
@@ -15,18 +15,16 @@
 package rpc
 
 import (
-	"time"
-
 	"github.com/hyperledger/burrow/acm"
 	"github.com/hyperledger/burrow/acm/validator"
 	"github.com/hyperledger/burrow/binary"
+	"github.com/hyperledger/burrow/consensus/tendermint"
 	"github.com/hyperledger/burrow/crypto"
 	"github.com/hyperledger/burrow/execution/names"
 	"github.com/hyperledger/burrow/genesis"
 	"github.com/hyperledger/burrow/txs"
 	"github.com/tendermint/go-amino"
 	consensusTypes "github.com/tendermint/tendermint/consensus/types"
-	"github.com/tendermint/tendermint/p2p"
 	"github.com/tendermint/tendermint/rpc/core/types"
 	tmTypes "github.com/tendermint/tendermint/types"
 )
@@ -94,22 +92,6 @@ func (b *Block) UnmarshalJSON(data []byte) (err error) {
 	return aminoCodec.UnmarshalJSON(data, &b.Block)
 }
 
-type ResultStatus struct {
-	NodeInfo          p2p.NodeInfo
-	GenesisHash       binary.HexBytes
-	PublicKey         crypto.PublicKey
-	LatestBlockHash   binary.HexBytes
-	LatestBlockHeight uint64
-	LatestBlockTime   int64
-	NodeVersion       string
-}
-
-type ResultLastBlockInfo struct {
-	LastBlockHeight uint64
-	LastBlockTime   time.Time
-	LastBlockHash   binary.HexBytes
-}
-
 type ResultChainId struct {
 	ChainName   string
 	ChainId     string
@@ -126,12 +108,12 @@ type ResultUnsubscribe struct {
 }
 
 type Peer struct {
-	NodeInfo   p2p.NodeInfo
+	NodeInfo   *tendermint.NodeInfo
 	IsOutbound bool
 }
 
 type ResultNetInfo struct {
-	ThisNode  p2p.NodeInfo
+	ThisNode  *tendermint.NodeInfo
 	Listening bool
 	Listeners []string
 	Peers     []*Peer
diff --git a/rpc/result_test.go b/rpc/result_test.go
index 9c66f47a..78af6663 100644
--- a/rpc/result_test.go
+++ b/rpc/result_test.go
@@ -18,12 +18,7 @@ import (
 	"encoding/json"
 	"testing"
 
-	"time"
-
-	"fmt"
-
 	"github.com/hyperledger/burrow/acm"
-	"github.com/hyperledger/burrow/binary"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/tendermint/tendermint/consensus/types"
@@ -87,15 +82,3 @@ func TestResultDumpConsensusState(t *testing.T) {
 	require.NoError(t, err)
 	assert.Equal(t, string(bs), string(bsOut))
 }
-
-func TestResultLastBlockInfo(t *testing.T) {
-	res := &ResultLastBlockInfo{
-		LastBlockTime:   time.Now(),
-		LastBlockHash:   binary.HexBytes{3, 4, 5, 6},
-		LastBlockHeight: 2343,
-	}
-	bs, err := json.Marshal(res)
-	require.NoError(t, err)
-	fmt.Println(string(bs))
-
-}
diff --git a/rpc/rpc.pb.go b/rpc/rpc.pb.go
new file mode 100644
index 00000000..8836266a
--- /dev/null
+++ b/rpc/rpc.pb.go
@@ -0,0 +1,652 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: rpc.proto
+
+/*
+	Package rpc is a generated protocol buffer package.
+
+	It is generated from these files:
+		rpc.proto
+
+	It has these top-level messages:
+		ResultStatus
+*/
+package rpc
+
+import proto "github.com/gogo/protobuf/proto"
+import golang_proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "github.com/gogo/protobuf/gogoproto"
+import crypto "github.com/hyperledger/burrow/crypto"
+import tendermint "github.com/hyperledger/burrow/consensus/tendermint"
+import _ "github.com/golang/protobuf/ptypes/timestamp"
+
+import github_com_hyperledger_burrow_binary "github.com/hyperledger/burrow/binary"
+import time "time"
+
+import types "github.com/gogo/protobuf/types"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = golang_proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+var _ = time.Kitchen
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+type ResultStatus struct {
+	ChainID           string                                        `protobuf:"bytes,1,opt,name=ChainID,proto3" json:"ChainID,omitempty"`
+	GenesisHash       github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,4,opt,name=GenesisHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"GenesisHash"`
+	NodeInfo          *tendermint.NodeInfo                          `protobuf:"bytes,2,opt,name=NodeInfo" json:"NodeInfo,omitempty"`
+	NodeVersion       string                                        `protobuf:"bytes,3,opt,name=NodeVersion,proto3" json:"NodeVersion,omitempty"`
+	PublicKey         crypto.PublicKey                              `protobuf:"bytes,5,opt,name=PublicKey" json:"PublicKey"`
+	LatestBlockHash   github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,6,opt,name=LatestBlockHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"LatestBlockHash"`
+	LatestBlockHeight uint64                                        `protobuf:"varint,7,opt,name=LatestBlockHeight,proto3" json:"LatestBlockHeight,omitempty"`
+	LatestBlockTime   time.Time                                     `protobuf:"bytes,8,opt,name=LatestBlockTime,stdtime" json:"LatestBlockTime"`
+}
+
+func (m *ResultStatus) Reset()                    { *m = ResultStatus{} }
+func (m *ResultStatus) String() string            { return proto.CompactTextString(m) }
+func (*ResultStatus) ProtoMessage()               {}
+func (*ResultStatus) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{0} }
+
+func (m *ResultStatus) GetChainID() string {
+	if m != nil {
+		return m.ChainID
+	}
+	return ""
+}
+
+func (m *ResultStatus) GetNodeInfo() *tendermint.NodeInfo {
+	if m != nil {
+		return m.NodeInfo
+	}
+	return nil
+}
+
+func (m *ResultStatus) GetNodeVersion() string {
+	if m != nil {
+		return m.NodeVersion
+	}
+	return ""
+}
+
+func (m *ResultStatus) GetPublicKey() crypto.PublicKey {
+	if m != nil {
+		return m.PublicKey
+	}
+	return crypto.PublicKey{}
+}
+
+func (m *ResultStatus) GetLatestBlockHeight() uint64 {
+	if m != nil {
+		return m.LatestBlockHeight
+	}
+	return 0
+}
+
+func (m *ResultStatus) GetLatestBlockTime() time.Time {
+	if m != nil {
+		return m.LatestBlockTime
+	}
+	return time.Time{}
+}
+
+func (*ResultStatus) XXX_MessageName() string {
+	return "rpc.ResultStatus"
+}
+func init() {
+	proto.RegisterType((*ResultStatus)(nil), "rpc.ResultStatus")
+	golang_proto.RegisterType((*ResultStatus)(nil), "rpc.ResultStatus")
+}
+func (m *ResultStatus) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ResultStatus) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if len(m.ChainID) > 0 {
+		dAtA[i] = 0xa
+		i++
+		i = encodeVarintRpc(dAtA, i, uint64(len(m.ChainID)))
+		i += copy(dAtA[i:], m.ChainID)
+	}
+	if m.NodeInfo != nil {
+		dAtA[i] = 0x12
+		i++
+		i = encodeVarintRpc(dAtA, i, uint64(m.NodeInfo.Size()))
+		n1, err := m.NodeInfo.MarshalTo(dAtA[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n1
+	}
+	if len(m.NodeVersion) > 0 {
+		dAtA[i] = 0x1a
+		i++
+		i = encodeVarintRpc(dAtA, i, uint64(len(m.NodeVersion)))
+		i += copy(dAtA[i:], m.NodeVersion)
+	}
+	dAtA[i] = 0x22
+	i++
+	i = encodeVarintRpc(dAtA, i, uint64(m.GenesisHash.Size()))
+	n2, err := m.GenesisHash.MarshalTo(dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n2
+	dAtA[i] = 0x2a
+	i++
+	i = encodeVarintRpc(dAtA, i, uint64(m.PublicKey.Size()))
+	n3, err := m.PublicKey.MarshalTo(dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n3
+	dAtA[i] = 0x32
+	i++
+	i = encodeVarintRpc(dAtA, i, uint64(m.LatestBlockHash.Size()))
+	n4, err := m.LatestBlockHash.MarshalTo(dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n4
+	if m.LatestBlockHeight != 0 {
+		dAtA[i] = 0x38
+		i++
+		i = encodeVarintRpc(dAtA, i, uint64(m.LatestBlockHeight))
+	}
+	dAtA[i] = 0x42
+	i++
+	i = encodeVarintRpc(dAtA, i, uint64(types.SizeOfStdTime(m.LatestBlockTime)))
+	n5, err := types.StdTimeMarshalTo(m.LatestBlockTime, dAtA[i:])
+	if err != nil {
+		return 0, err
+	}
+	i += n5
+	return i, nil
+}
+
+func encodeVarintRpc(dAtA []byte, offset int, v uint64) int {
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return offset + 1
+}
+func (m *ResultStatus) Size() (n int) {
+	var l int
+	_ = l
+	l = len(m.ChainID)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	if m.NodeInfo != nil {
+		l = m.NodeInfo.Size()
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	l = len(m.NodeVersion)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	l = m.GenesisHash.Size()
+	n += 1 + l + sovRpc(uint64(l))
+	l = m.PublicKey.Size()
+	n += 1 + l + sovRpc(uint64(l))
+	l = m.LatestBlockHash.Size()
+	n += 1 + l + sovRpc(uint64(l))
+	if m.LatestBlockHeight != 0 {
+		n += 1 + sovRpc(uint64(m.LatestBlockHeight))
+	}
+	l = types.SizeOfStdTime(m.LatestBlockTime)
+	n += 1 + l + sovRpc(uint64(l))
+	return n
+}
+
+func sovRpc(x uint64) (n int) {
+	for {
+		n++
+		x >>= 7
+		if x == 0 {
+			break
+		}
+	}
+	return n
+}
+func sozRpc(x uint64) (n int) {
+	return sovRpc(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *ResultStatus) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowRpc
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ResultStatus: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ResultStatus: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ChainID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ChainID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field NodeInfo", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.NodeInfo == nil {
+				m.NodeInfo = &tendermint.NodeInfo{}
+			}
+			if err := m.NodeInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field NodeVersion", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.NodeVersion = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field GenesisHash", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.GenesisHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.PublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 6:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LatestBlockHash", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.LatestBlockHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 7:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LatestBlockHeight", wireType)
+			}
+			m.LatestBlockHeight = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.LatestBlockHeight |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 8:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LatestBlockTime", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := types.StdTimeUnmarshal(&m.LatestBlockTime, dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipRpc(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthRpc
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipRpc(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowRpc
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+			return iNdEx, nil
+		case 1:
+			iNdEx += 8
+			return iNdEx, nil
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			iNdEx += length
+			if length < 0 {
+				return 0, ErrInvalidLengthRpc
+			}
+			return iNdEx, nil
+		case 3:
+			for {
+				var innerWire uint64
+				var start int = iNdEx
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return 0, ErrIntOverflowRpc
+					}
+					if iNdEx >= l {
+						return 0, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					innerWire |= (uint64(b) & 0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				innerWireType := int(innerWire & 0x7)
+				if innerWireType == 4 {
+					break
+				}
+				next, err := skipRpc(dAtA[start:])
+				if err != nil {
+					return 0, err
+				}
+				iNdEx = start + next
+			}
+			return iNdEx, nil
+		case 4:
+			return iNdEx, nil
+		case 5:
+			iNdEx += 4
+			return iNdEx, nil
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+	}
+	panic("unreachable")
+}
+
+var (
+	ErrInvalidLengthRpc = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowRpc   = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("rpc.proto", fileDescriptorRpc) }
+func init() { golang_proto.RegisterFile("rpc.proto", fileDescriptorRpc) }
+
+var fileDescriptorRpc = []byte{
+	// 397 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x51, 0x41, 0xcf, 0xd2, 0x40,
+	0x10, 0x75, 0x05, 0xbf, 0x0f, 0x16, 0x12, 0x65, 0xe3, 0x61, 0xc3, 0xa1, 0xad, 0x9e, 0x7a, 0xd0,
+	0xad, 0xd1, 0x10, 0xef, 0xd5, 0x44, 0x88, 0x86, 0x98, 0x4a, 0x34, 0xf1, 0x62, 0xda, 0x32, 0xb4,
+	0x1b, 0xdb, 0x6e, 0xb3, 0xbb, 0x8d, 0xf6, 0x5f, 0xf8, 0x93, 0x3c, 0x72, 0xf4, 0xec, 0x01, 0x0d,
+	0x1c, 0xfd, 0x13, 0x86, 0x85, 0x42, 0xc5, 0xc4, 0x8b, 0xb7, 0x79, 0xf3, 0x66, 0xde, 0xbc, 0x99,
+	0xc1, 0x7d, 0x59, 0xc6, 0xac, 0x94, 0x42, 0x0b, 0xd2, 0x91, 0x65, 0x3c, 0x7e, 0x98, 0x70, 0x9d,
+	0x56, 0x11, 0x8b, 0x45, 0xee, 0x25, 0x22, 0x11, 0x9e, 0xe1, 0xa2, 0x6a, 0x65, 0x90, 0x01, 0x26,
+	0x3a, 0xf4, 0x8c, 0x87, 0xb1, 0xac, 0x4b, 0xdd, 0xa0, 0x3b, 0x1a, 0x8a, 0x25, 0xc8, 0x9c, 0x17,
+	0xfa, 0x98, 0xb1, 0x13, 0x21, 0x92, 0x0c, 0xce, 0x2a, 0x9a, 0xe7, 0xa0, 0x74, 0x98, 0x97, 0x87,
+	0x82, 0xfb, 0xbf, 0x3a, 0x78, 0x18, 0x80, 0xaa, 0x32, 0xfd, 0x46, 0x87, 0xba, 0x52, 0x84, 0xe2,
+	0xeb, 0x67, 0x69, 0xc8, 0x8b, 0xd9, 0x73, 0x8a, 0x1c, 0xe4, 0xf6, 0x83, 0x06, 0x92, 0x47, 0xb8,
+	0x37, 0x17, 0x4b, 0x98, 0x15, 0x2b, 0x41, 0x6f, 0x3a, 0xc8, 0x1d, 0x3c, 0xbe, 0xcb, 0x5a, 0x03,
+	0x1b, 0x2e, 0x38, 0x55, 0x11, 0x07, 0x0f, 0xf6, 0xf1, 0x5b, 0x90, 0x8a, 0x8b, 0x82, 0x76, 0x8c,
+	0x5e, 0x3b, 0x45, 0xde, 0xe1, 0xc1, 0x0b, 0x28, 0x40, 0x71, 0x35, 0x0d, 0x55, 0x4a, 0xbb, 0x0e,
+	0x72, 0x87, 0xfe, 0x64, 0xbd, 0xb1, 0x6f, 0x7c, 0xdf, 0xd8, 0xed, 0x5b, 0xa4, 0x75, 0x09, 0x32,
+	0x83, 0x65, 0x02, 0xd2, 0x8b, 0x2a, 0x29, 0xc5, 0x27, 0x2f, 0xe2, 0x45, 0x28, 0x6b, 0x36, 0x85,
+	0xcf, 0x7e, 0xad, 0x41, 0x05, 0x6d, 0x25, 0x32, 0xc1, 0xfd, 0xd7, 0x55, 0x94, 0xf1, 0xf8, 0x25,
+	0xd4, 0xf4, 0x96, 0x71, 0x3b, 0x62, 0xc7, 0x63, 0x9d, 0x08, 0xbf, 0xbb, 0x9f, 0x14, 0x9c, 0x2b,
+	0xc9, 0x07, 0x7c, 0xfb, 0x55, 0xa8, 0x41, 0x69, 0x3f, 0x13, 0xf1, 0x47, 0xe3, 0xe9, 0xea, 0x7f,
+	0x3c, 0x5d, 0xaa, 0x91, 0x07, 0x78, 0xd4, 0x4e, 0x01, 0x4f, 0x52, 0x4d, 0xaf, 0x1d, 0xe4, 0x76,
+	0x83, 0xbf, 0x09, 0x32, 0xff, 0xc3, 0xce, 0x82, 0xe7, 0x40, 0x7b, 0x66, 0x97, 0x31, 0x3b, 0x3c,
+	0x96, 0x35, 0x8f, 0x65, 0x8b, 0xe6, 0xb1, 0x7e, 0x6f, 0x6f, 0xf5, 0xcb, 0x0f, 0x1b, 0x05, 0x97,
+	0xcd, 0xfe, 0xd3, 0xf5, 0xd6, 0x42, 0xdf, 0xb6, 0x16, 0xfa, 0xb9, 0xb5, 0xd0, 0xd7, 0x9d, 0x85,
+	0xd6, 0x3b, 0x0b, 0xbd, 0xbf, 0xf7, 0xef, 0x9d, 0x64, 0x19, 0x47, 0x57, 0x66, 0xce, 0x93, 0xdf,
+	0x01, 0x00, 0x00, 0xff, 0xff, 0x84, 0x6f, 0xc9, 0xec, 0xaf, 0x02, 0x00, 0x00,
+}
diff --git a/rpc/rpcquery/query_server.go b/rpc/rpcquery/query_server.go
index debdc05b..45f2a54e 100644
--- a/rpc/rpcquery/query_server.go
+++ b/rpc/rpcquery/query_server.go
@@ -10,6 +10,7 @@ import (
 	"github.com/hyperledger/burrow/event/query"
 	"github.com/hyperledger/burrow/execution/names"
 	"github.com/hyperledger/burrow/logging"
+	"github.com/hyperledger/burrow/rpc"
 )
 
 type queryServer struct {
@@ -33,6 +34,10 @@ func NewQueryServer(state state.IterableReader, nameReg names.IterableReader, bl
 	}
 }
 
+func (qs *queryServer) Status(ctx context.Context, param *StatusParam) (*rpc.ResultStatus, error) {
+	return rpc.Status(qs.blockchain, qs.nodeView, param.BlockWithin)
+}
+
 // Account state
 
 func (qs *queryServer) GetAccount(ctx context.Context, param *GetAccountParam) (*acm.ConcreteAccount, error) {
diff --git a/rpc/rpcquery/rpcquery.pb.go b/rpc/rpcquery/rpcquery.pb.go
index ec9b8171..120b2df0 100644
--- a/rpc/rpcquery/rpcquery.pb.go
+++ b/rpc/rpcquery/rpcquery.pb.go
@@ -8,6 +8,7 @@
 		rpcquery.proto
 
 	It has these top-level messages:
+		StatusParam
 		GetAccountParam
 		ListAccountsParam
 		GetNameParam
@@ -26,6 +27,7 @@ import _ "github.com/gogo/protobuf/gogoproto"
 import names "github.com/hyperledger/burrow/execution/names"
 import acm "github.com/hyperledger/burrow/acm"
 import validator "github.com/hyperledger/burrow/acm/validator"
+import rpc "github.com/hyperledger/burrow/rpc"
 
 import github_com_hyperledger_burrow_crypto "github.com/hyperledger/burrow/crypto"
 
@@ -46,6 +48,26 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
 
+type StatusParam struct {
+	BlockWithin string `protobuf:"bytes,1,opt,name=BlockWithin,proto3" json:"BlockWithin,omitempty"`
+}
+
+func (m *StatusParam) Reset()                    { *m = StatusParam{} }
+func (m *StatusParam) String() string            { return proto.CompactTextString(m) }
+func (*StatusParam) ProtoMessage()               {}
+func (*StatusParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{0} }
+
+func (m *StatusParam) GetBlockWithin() string {
+	if m != nil {
+		return m.BlockWithin
+	}
+	return ""
+}
+
+func (*StatusParam) XXX_MessageName() string {
+	return "rpcquery.StatusParam"
+}
+
 type GetAccountParam struct {
 	Address github_com_hyperledger_burrow_crypto.Address `protobuf:"bytes,1,opt,name=Address,proto3,customtype=github.com/hyperledger/burrow/crypto.Address" json:"Address"`
 }
@@ -53,7 +75,7 @@ type GetAccountParam struct {
 func (m *GetAccountParam) Reset()                    { *m = GetAccountParam{} }
 func (m *GetAccountParam) String() string            { return proto.CompactTextString(m) }
 func (*GetAccountParam) ProtoMessage()               {}
-func (*GetAccountParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{0} }
+func (*GetAccountParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{1} }
 
 func (*GetAccountParam) XXX_MessageName() string {
 	return "rpcquery.GetAccountParam"
@@ -66,7 +88,7 @@ type ListAccountsParam struct {
 func (m *ListAccountsParam) Reset()                    { *m = ListAccountsParam{} }
 func (m *ListAccountsParam) String() string            { return proto.CompactTextString(m) }
 func (*ListAccountsParam) ProtoMessage()               {}
-func (*ListAccountsParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{1} }
+func (*ListAccountsParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{2} }
 
 func (m *ListAccountsParam) GetQuery() string {
 	if m != nil {
@@ -86,7 +108,7 @@ type GetNameParam struct {
 func (m *GetNameParam) Reset()                    { *m = GetNameParam{} }
 func (m *GetNameParam) String() string            { return proto.CompactTextString(m) }
 func (*GetNameParam) ProtoMessage()               {}
-func (*GetNameParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{2} }
+func (*GetNameParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{3} }
 
 func (m *GetNameParam) GetName() string {
 	if m != nil {
@@ -106,7 +128,7 @@ type ListNamesParam struct {
 func (m *ListNamesParam) Reset()                    { *m = ListNamesParam{} }
 func (m *ListNamesParam) String() string            { return proto.CompactTextString(m) }
 func (*ListNamesParam) ProtoMessage()               {}
-func (*ListNamesParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{3} }
+func (*ListNamesParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{4} }
 
 func (m *ListNamesParam) GetQuery() string {
 	if m != nil {
@@ -126,7 +148,7 @@ type GetValidatorSetParam struct {
 func (m *GetValidatorSetParam) Reset()                    { *m = GetValidatorSetParam{} }
 func (m *GetValidatorSetParam) String() string            { return proto.CompactTextString(m) }
 func (*GetValidatorSetParam) ProtoMessage()               {}
-func (*GetValidatorSetParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{4} }
+func (*GetValidatorSetParam) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{5} }
 
 func (m *GetValidatorSetParam) GetIncludeHistory() bool {
 	if m != nil {
@@ -148,7 +170,7 @@ type ValidatorSet struct {
 func (m *ValidatorSet) Reset()                    { *m = ValidatorSet{} }
 func (m *ValidatorSet) String() string            { return proto.CompactTextString(m) }
 func (*ValidatorSet) ProtoMessage()               {}
-func (*ValidatorSet) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{5} }
+func (*ValidatorSet) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{6} }
 
 func (m *ValidatorSet) GetHeight() uint64 {
 	if m != nil {
@@ -182,7 +204,7 @@ type ValidatorSetDeltas struct {
 func (m *ValidatorSetDeltas) Reset()                    { *m = ValidatorSetDeltas{} }
 func (m *ValidatorSetDeltas) String() string            { return proto.CompactTextString(m) }
 func (*ValidatorSetDeltas) ProtoMessage()               {}
-func (*ValidatorSetDeltas) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{6} }
+func (*ValidatorSetDeltas) Descriptor() ([]byte, []int) { return fileDescriptorRpcquery, []int{7} }
 
 func (m *ValidatorSetDeltas) GetValidators() []*validator.Validator {
 	if m != nil {
@@ -195,6 +217,8 @@ func (*ValidatorSetDeltas) XXX_MessageName() string {
 	return "rpcquery.ValidatorSetDeltas"
 }
 func init() {
+	proto.RegisterType((*StatusParam)(nil), "rpcquery.StatusParam")
+	golang_proto.RegisterType((*StatusParam)(nil), "rpcquery.StatusParam")
 	proto.RegisterType((*GetAccountParam)(nil), "rpcquery.GetAccountParam")
 	golang_proto.RegisterType((*GetAccountParam)(nil), "rpcquery.GetAccountParam")
 	proto.RegisterType((*ListAccountsParam)(nil), "rpcquery.ListAccountsParam")
@@ -222,6 +246,7 @@ const _ = grpc.SupportPackageIsVersion4
 // Client API for Query service
 
 type QueryClient interface {
+	Status(ctx context.Context, in *StatusParam, opts ...grpc.CallOption) (*rpc.ResultStatus, error)
 	GetAccount(ctx context.Context, in *GetAccountParam, opts ...grpc.CallOption) (*acm.ConcreteAccount, error)
 	ListAccounts(ctx context.Context, in *ListAccountsParam, opts ...grpc.CallOption) (Query_ListAccountsClient, error)
 	GetName(ctx context.Context, in *GetNameParam, opts ...grpc.CallOption) (*names.Entry, error)
@@ -237,6 +262,15 @@ func NewQueryClient(cc *grpc.ClientConn) QueryClient {
 	return &queryClient{cc}
 }
 
+func (c *queryClient) Status(ctx context.Context, in *StatusParam, opts ...grpc.CallOption) (*rpc.ResultStatus, error) {
+	out := new(rpc.ResultStatus)
+	err := grpc.Invoke(ctx, "/rpcquery.Query/Status", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *queryClient) GetAccount(ctx context.Context, in *GetAccountParam, opts ...grpc.CallOption) (*acm.ConcreteAccount, error) {
 	out := new(acm.ConcreteAccount)
 	err := grpc.Invoke(ctx, "/rpcquery.Query/GetAccount", in, out, c.cc, opts...)
@@ -331,6 +365,7 @@ func (c *queryClient) GetValidatorSet(ctx context.Context, in *GetValidatorSetPa
 // Server API for Query service
 
 type QueryServer interface {
+	Status(context.Context, *StatusParam) (*rpc.ResultStatus, error)
 	GetAccount(context.Context, *GetAccountParam) (*acm.ConcreteAccount, error)
 	ListAccounts(*ListAccountsParam, Query_ListAccountsServer) error
 	GetName(context.Context, *GetNameParam) (*names.Entry, error)
@@ -342,6 +377,24 @@ func RegisterQueryServer(s *grpc.Server, srv QueryServer) {
 	s.RegisterService(&_Query_serviceDesc, srv)
 }
 
+func _Query_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(StatusParam)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).Status(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/rpcquery.Query/Status",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).Status(ctx, req.(*StatusParam))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _Query_GetAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 	in := new(GetAccountParam)
 	if err := dec(in); err != nil {
@@ -442,6 +495,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "rpcquery.Query",
 	HandlerType: (*QueryServer)(nil),
 	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Status",
+			Handler:    _Query_Status_Handler,
+		},
 		{
 			MethodName: "GetAccount",
 			Handler:    _Query_GetAccount_Handler,
@@ -470,6 +527,30 @@ var _Query_serviceDesc = grpc.ServiceDesc{
 	Metadata: "rpcquery.proto",
 }
 
+func (m *StatusParam) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *StatusParam) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if len(m.BlockWithin) > 0 {
+		dAtA[i] = 0xa
+		i++
+		i = encodeVarintRpcquery(dAtA, i, uint64(len(m.BlockWithin)))
+		i += copy(dAtA[i:], m.BlockWithin)
+	}
+	return i, nil
+}
+
 func (m *GetAccountParam) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -682,6 +763,16 @@ func encodeVarintRpcquery(dAtA []byte, offset int, v uint64) int {
 	dAtA[offset] = uint8(v)
 	return offset + 1
 }
+func (m *StatusParam) Size() (n int) {
+	var l int
+	_ = l
+	l = len(m.BlockWithin)
+	if l > 0 {
+		n += 1 + l + sovRpcquery(uint64(l))
+	}
+	return n
+}
+
 func (m *GetAccountParam) Size() (n int) {
 	var l int
 	_ = l
@@ -775,6 +866,85 @@ func sovRpcquery(x uint64) (n int) {
 func sozRpcquery(x uint64) (n int) {
 	return sovRpcquery(uint64((x << 1) ^ uint64((int64(x) >> 63))))
 }
+func (m *StatusParam) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowRpcquery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: StatusParam: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: StatusParam: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field BlockWithin", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpcquery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRpcquery
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.BlockWithin = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipRpcquery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthRpcquery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *GetAccountParam) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -1483,36 +1653,39 @@ func init() { proto.RegisterFile("rpcquery.proto", fileDescriptorRpcquery) }
 func init() { golang_proto.RegisterFile("rpcquery.proto", fileDescriptorRpcquery) }
 
 var fileDescriptorRpcquery = []byte{
-	// 487 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x4f, 0x6f, 0xd3, 0x30,
-	0x1c, 0xc5, 0xeb, 0x58, 0xd7, 0xdf, 0xaa, 0x4e, 0x58, 0x55, 0x55, 0x02, 0xca, 0xa6, 0x1c, 0xaa,
-	0x81, 0x20, 0x99, 0xc6, 0xe0, 0x06, 0x62, 0x1b, 0xa8, 0x0c, 0xa1, 0x09, 0x32, 0x89, 0x03, 0x37,
-	0xd7, 0x31, 0x69, 0xa4, 0x26, 0x0e, 0x8e, 0x03, 0xea, 0x17, 0xe0, 0x63, 0x21, 0x8e, 0x3d, 0x72,
-	0xe6, 0x30, 0xa1, 0xee, 0x8b, 0xa0, 0x38, 0xce, 0xbf, 0x75, 0xea, 0xcd, 0xcf, 0x79, 0xef, 0xf7,
-	0xec, 0xbc, 0x67, 0xe8, 0x89, 0x98, 0x7e, 0x4b, 0x99, 0x98, 0xdb, 0xb1, 0xe0, 0x92, 0xe3, 0xed,
-	0x02, 0x1b, 0x4f, 0xfd, 0x40, 0x4e, 0xd3, 0x89, 0x4d, 0x79, 0xe8, 0xf8, 0xdc, 0xe7, 0x8e, 0x22,
-	0x4c, 0xd2, 0xaf, 0x0a, 0x29, 0xa0, 0x56, 0xb9, 0xd0, 0xd8, 0x89, 0x48, 0xc8, 0x12, 0x0d, 0x3a,
-	0x84, 0x86, 0x7a, 0xb9, 0xfb, 0x9d, 0xcc, 0x02, 0x8f, 0x48, 0x2e, 0xf2, 0x0d, 0x8b, 0xc0, 0xee,
-	0x98, 0xc9, 0x13, 0x4a, 0x79, 0x1a, 0xc9, 0x8f, 0x44, 0x90, 0x10, 0x5f, 0x40, 0xfb, 0xc4, 0xf3,
-	0x04, 0x4b, 0x92, 0x21, 0xda, 0x47, 0x07, 0xdd, 0xd3, 0xe3, 0xc5, 0xd5, 0xde, 0x9d, 0xbf, 0x57,
-	0x7b, 0x4f, 0x6a, 0x67, 0x98, 0xce, 0x63, 0x26, 0x66, 0xcc, 0xf3, 0x99, 0x70, 0x26, 0xa9, 0x10,
-	0xfc, 0x87, 0x43, 0xc5, 0x3c, 0x96, 0xdc, 0xd6, 0x5a, 0xb7, 0x18, 0x62, 0x3d, 0x82, 0x7b, 0x1f,
-	0x82, 0xa4, 0xf0, 0x48, 0x72, 0x93, 0x3e, 0xdc, 0xfd, 0x94, 0x5d, 0x4c, 0x59, 0x74, 0xdc, 0x1c,
-	0x58, 0x16, 0x74, 0xc7, 0x4c, 0x5e, 0x90, 0x90, 0xe5, 0x2c, 0x0c, 0x9b, 0x19, 0xd0, 0x24, 0xb5,
-	0xb6, 0x46, 0xd0, 0xcb, 0xc6, 0x65, 0xeb, 0xb5, 0xb3, 0x5e, 0x41, 0x7f, 0xcc, 0xe4, 0xe7, 0xe2,
-	0xbe, 0x97, 0x4c, 0x5f, 0x6f, 0x04, 0xbd, 0xf3, 0x88, 0xce, 0x52, 0x8f, 0xbd, 0x0b, 0x12, 0xc9,
-	0xb5, 0x6c, 0xdb, 0xbd, 0xb1, 0x6b, 0xfd, 0x44, 0xd0, 0xad, 0xab, 0xf1, 0x00, 0xb6, 0xa6, 0x2c,
-	0xf0, 0xa7, 0x52, 0x09, 0x36, 0x5d, 0x8d, 0xf0, 0x08, 0x5a, 0x97, 0x4c, 0x0e, 0x37, 0xf6, 0x5b,
-	0x07, 0x3b, 0x47, 0x7d, 0xbb, 0xfa, 0xc3, 0xa5, 0xda, 0xcd, 0x08, 0xf8, 0x05, 0xb4, 0x0b, 0xc7,
-	0x96, 0xe2, 0x3e, 0xb4, 0xcb, 0xb8, 0xeb, 0x46, 0x6f, 0xd8, 0x4c, 0x92, 0xc4, 0x2d, 0xc8, 0xd6,
-	0x7b, 0xc0, 0xab, 0x9f, 0xf1, 0x31, 0x40, 0xb9, 0x9b, 0xac, 0x35, 0xaf, 0xf1, 0x8e, 0x7e, 0x6d,
-	0xe8, 0x7f, 0x85, 0x5f, 0x02, 0x54, 0xc1, 0xe3, 0xfb, 0xd5, 0x51, 0x6e, 0xd4, 0xc1, 0xe8, 0xdb,
-	0x59, 0x7d, 0xce, 0x78, 0x44, 0x05, 0x93, 0xac, 0x10, 0x9c, 0x41, 0xb7, 0x1e, 0x2a, 0x7e, 0x50,
-	0x0d, 0x58, 0x09, 0xfb, 0xf6, 0x11, 0x87, 0x08, 0x3b, 0xd0, 0xd6, 0x71, 0xe3, 0x41, 0xe3, 0x00,
-	0x65, 0x03, 0x8c, 0xae, 0x9d, 0x37, 0xf9, 0x6d, 0x24, 0xc5, 0x1c, 0x3f, 0x87, 0x4e, 0x99, 0x3d,
-	0x1e, 0x36, 0x2d, 0xab, 0x42, 0x34, 0x45, 0x87, 0x08, 0x9f, 0xab, 0x92, 0x37, 0xc2, 0x34, 0x1b,
-	0x7e, 0x2b, 0x2d, 0x31, 0x06, 0xb7, 0x67, 0x73, 0xfa, 0x7a, 0xb1, 0x34, 0xd1, 0x9f, 0xa5, 0x89,
-	0xfe, 0x2d, 0x4d, 0xf4, 0xfb, 0xda, 0x44, 0x8b, 0x6b, 0x13, 0x7d, 0x79, 0xbc, 0xfe, 0x65, 0x88,
-	0x98, 0x3a, 0xc5, 0xb8, 0xc9, 0x96, 0x7a, 0x78, 0xcf, 0xfe, 0x07, 0x00, 0x00, 0xff, 0xff, 0xfd,
-	0xa4, 0xb6, 0xf9, 0xec, 0x03, 0x00, 0x00,
+	// 538 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xc1, 0x6e, 0xd3, 0x40,
+	0x10, 0x86, 0xd9, 0xa6, 0x24, 0xcd, 0x24, 0x4a, 0xd5, 0x55, 0x88, 0x82, 0x41, 0x6e, 0xe4, 0x43,
+	0x54, 0x10, 0xd8, 0x55, 0x28, 0xdc, 0x40, 0x34, 0x05, 0x85, 0x22, 0x54, 0x81, 0x23, 0x81, 0xc4,
+	0x6d, 0xb3, 0x59, 0x12, 0x0b, 0xc7, 0x6b, 0xd6, 0x6b, 0x50, 0x5e, 0x80, 0xe7, 0xe2, 0x98, 0x23,
+	0x67, 0x0e, 0x15, 0x4a, 0xdf, 0x80, 0x27, 0x40, 0x5e, 0xaf, 0x63, 0xa7, 0xa9, 0x72, 0x9b, 0x59,
+	0xff, 0xff, 0xcc, 0x78, 0xf7, 0x1b, 0x68, 0x88, 0x90, 0x7e, 0x8b, 0x99, 0x98, 0xdb, 0xa1, 0xe0,
+	0x92, 0xe3, 0xbd, 0x2c, 0x37, 0x1e, 0x4f, 0x3c, 0x39, 0x8d, 0x47, 0x36, 0xe5, 0x33, 0x67, 0xc2,
+	0x27, 0xdc, 0x51, 0x82, 0x51, 0xfc, 0x45, 0x65, 0x2a, 0x51, 0x51, 0x6a, 0x34, 0x6a, 0x01, 0x99,
+	0xb1, 0x48, 0x27, 0x55, 0x42, 0x67, 0x3a, 0xdc, 0xff, 0x4e, 0x7c, 0x6f, 0x4c, 0x24, 0x17, 0xd9,
+	0x37, 0x11, 0xd2, 0x34, 0xb4, 0x1c, 0xa8, 0x0d, 0x25, 0x91, 0x71, 0xf4, 0x9e, 0x08, 0x32, 0xc3,
+	0x1d, 0xa8, 0xf5, 0x7d, 0x4e, 0xbf, 0x7e, 0xf2, 0xe4, 0xd4, 0x0b, 0xda, 0xa8, 0x83, 0x8e, 0xaa,
+	0x6e, 0xf1, 0xc8, 0x22, 0xb0, 0x3f, 0x60, 0xf2, 0x94, 0x52, 0x1e, 0x07, 0x32, 0x35, 0x5d, 0x40,
+	0xe5, 0x74, 0x3c, 0x16, 0x2c, 0x8a, 0x94, 0xa1, 0xde, 0x3f, 0x59, 0x5c, 0x1e, 0xde, 0xfa, 0x73,
+	0x79, 0xf8, 0xa8, 0x30, 0xff, 0x74, 0x1e, 0x32, 0xe1, 0xb3, 0xf1, 0x84, 0x09, 0x67, 0x14, 0x0b,
+	0xc1, 0x7f, 0x38, 0x54, 0xcc, 0x43, 0xc9, 0x6d, 0xed, 0x75, 0xb3, 0x22, 0xd6, 0x03, 0x38, 0x78,
+	0xe7, 0x45, 0x59, 0x0f, 0x3d, 0x59, 0x13, 0x6e, 0x7f, 0x48, 0x2e, 0x45, 0xcf, 0x94, 0x26, 0x96,
+	0x05, 0xf5, 0x01, 0x93, 0x17, 0x64, 0xc6, 0x52, 0x15, 0x86, 0xdd, 0x24, 0xd1, 0x22, 0x15, 0x5b,
+	0x5d, 0x68, 0x24, 0xe5, 0x92, 0x78, 0x6b, 0xad, 0x17, 0xd0, 0x1c, 0x30, 0xf9, 0x31, 0xbb, 0xab,
+	0x21, 0xd3, 0xbf, 0xd7, 0x85, 0xc6, 0x79, 0x40, 0xfd, 0x78, 0xcc, 0xde, 0x78, 0x91, 0xe4, 0xda,
+	0xb6, 0xe7, 0x5e, 0x3b, 0xb5, 0x7e, 0x22, 0xa8, 0x17, 0xdd, 0xb8, 0x05, 0xe5, 0x29, 0xf3, 0x26,
+	0x53, 0xa9, 0x0c, 0xbb, 0xae, 0xce, 0x70, 0x17, 0x4a, 0x43, 0x26, 0xdb, 0x3b, 0x9d, 0xd2, 0x51,
+	0xad, 0xd7, 0xb4, 0xf3, 0xd7, 0x59, 0xb9, 0xdd, 0x44, 0x80, 0x9f, 0x41, 0x25, 0xeb, 0x58, 0x52,
+	0xda, 0xfb, 0xf6, 0x0a, 0x95, 0x62, 0xa3, 0x57, 0xcc, 0x97, 0x24, 0x72, 0x33, 0xb1, 0xf5, 0x16,
+	0xf0, 0xe6, 0x67, 0x7c, 0x02, 0xb0, 0x3a, 0x8d, 0xb6, 0x36, 0x2f, 0xe8, 0x7a, 0xff, 0x76, 0xf4,
+	0x5d, 0xe1, 0x1e, 0x94, 0x53, 0x52, 0xf0, 0x9d, 0x7c, 0x8c, 0x02, 0x3b, 0xc6, 0x41, 0x72, 0x6c,
+	0xbb, 0x2c, 0x8a, 0x7d, 0xa9, 0x95, 0xcf, 0x01, 0x72, 0x58, 0xf0, 0xdd, 0xdc, 0x77, 0x0d, 0x21,
+	0xa3, 0x69, 0x27, 0xb8, 0x9e, 0xf1, 0x80, 0x0a, 0x26, 0x59, 0x66, 0x38, 0x83, 0x7a, 0x11, 0x04,
+	0x7c, 0x2f, 0x2f, 0xb0, 0x01, 0xc8, 0xcd, 0x25, 0x8e, 0x11, 0x76, 0xa0, 0xa2, 0x11, 0xc1, 0xad,
+	0xb5, 0x01, 0x56, 0xd4, 0x18, 0x75, 0x3b, 0xdd, 0x9c, 0xd7, 0x81, 0x14, 0x73, 0xfc, 0x14, 0xaa,
+	0x2b, 0x5e, 0x70, 0x7b, 0xbd, 0x65, 0x0e, 0xd1, 0xba, 0xe9, 0x18, 0xe1, 0x73, 0xb5, 0x18, 0x6b,
+	0x00, 0x98, 0x6b, 0xfd, 0x36, 0xc8, 0x32, 0x5a, 0x37, 0xbf, 0x67, 0xff, 0xe5, 0x62, 0x69, 0xa2,
+	0xdf, 0x4b, 0x13, 0xfd, 0x5d, 0x9a, 0xe8, 0xd7, 0x95, 0x89, 0x16, 0x57, 0x26, 0xfa, 0xfc, 0x70,
+	0xfb, 0x36, 0x89, 0x90, 0x3a, 0x59, 0xb9, 0x51, 0x59, 0x6d, 0xf7, 0x93, 0xff, 0x01, 0x00, 0x00,
+	0xff, 0xff, 0xb5, 0xb5, 0x70, 0xe2, 0x5c, 0x04, 0x00, 0x00,
 }
diff --git a/rpc/service.go b/rpc/service.go
index 3c266ad2..031a5f33 100644
--- a/rpc/service.go
+++ b/rpc/service.go
@@ -91,31 +91,8 @@ func (s *Service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, err
 	}, nil
 }
 
-func (s *Service) Status() (*ResultStatus, error) {
-	latestHeight := s.blockchain.LastBlockHeight()
-	var (
-		latestBlockMeta *tmTypes.BlockMeta
-		latestBlockHash []byte
-		latestBlockTime int64
-	)
-	if latestHeight != 0 {
-		latestBlockMeta = s.nodeView.BlockStore().LoadBlockMeta(int64(latestHeight))
-		latestBlockHash = latestBlockMeta.Header.Hash()
-		latestBlockTime = latestBlockMeta.Header.Time.UnixNano()
-	}
-	publicKey, err := s.nodeView.PrivValidatorPublicKey()
-	if err != nil {
-		return nil, err
-	}
-	return &ResultStatus{
-		NodeInfo:          s.nodeView.NodeInfo(),
-		GenesisHash:       s.blockchain.GenesisHash(),
-		PublicKey:         publicKey,
-		LatestBlockHash:   latestBlockHash,
-		LatestBlockHeight: latestHeight,
-		LatestBlockTime:   latestBlockTime,
-		NodeVersion:       project.History.CurrentVersion().String(),
-	}, nil
+func (s *Service) Status(blockWithin string) (*ResultStatus, error) {
+	return Status(s.BlockchainInfo(), s.nodeView, blockWithin)
 }
 
 func (s *Service) ChainIdentifiers() (*ResultChainId, error) {
@@ -130,7 +107,7 @@ func (s *Service) Peers() (*ResultPeers, error) {
 	peers := make([]*Peer, s.nodeView.Peers().Size())
 	for i, peer := range s.nodeView.Peers().List() {
 		peers[i] = &Peer{
-			NodeInfo:   peer.NodeInfo(),
+			NodeInfo:   tendermint.NewNodeInfo(peer.NodeInfo()),
 			IsOutbound: peer.IsOutbound(),
 		}
 	}
@@ -355,11 +332,16 @@ func (s *Service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error
 	}, nil
 }
 
-func (s *Service) LastBlockInfo(blockWithin string) (*ResultLastBlockInfo, error) {
-	res := &ResultLastBlockInfo{
-		LastBlockHeight: s.blockchain.LastBlockHeight(),
-		LastBlockHash:   s.blockchain.LastBlockHash(),
-		LastBlockTime:   s.blockchain.LastBlockTime(),
+func Status(blockchain bcm.BlockchainInfo, nodeView *tendermint.NodeView, blockWithin string) (*ResultStatus, error) {
+	res := &ResultStatus{
+		ChainID:           blockchain.ChainID(),
+		NodeInfo:          nodeView.NodeInfo(),
+		NodeVersion:       project.History.CurrentVersion().String(),
+		GenesisHash:       blockchain.GenesisHash(),
+		PublicKey:         nodeView.ValidatorPublicKey(),
+		LatestBlockHash:   blockchain.LastBlockHash(),
+		LatestBlockHeight: blockchain.LastBlockHeight(),
+		LatestBlockTime:   blockchain.LastBlockTime(),
 	}
 	if blockWithin == "" {
 		return res, nil
@@ -368,12 +350,12 @@ func (s *Service) LastBlockInfo(blockWithin string) (*ResultLastBlockInfo, error
 	if err != nil {
 		return nil, fmt.Errorf("could not parse blockWithin duration to determine whether to throw error: %v", err)
 	}
-	// Take neg abs in case caller is counting backwards (not we add later)
+	// Take neg abs in case caller is counting backwards (note we later add the time since we normalise the duration to negative)
 	if duration > 0 {
 		duration = -duration
 	}
 	blockTimeThreshold := time.Now().Add(duration)
-	if res.LastBlockTime.After(blockTimeThreshold) {
+	if res.LatestBlockTime.After(blockTimeThreshold) {
 		// We've created blocks recently enough
 		return res, nil
 	}
@@ -381,6 +363,6 @@ func (s *Service) LastBlockInfo(blockWithin string) (*ResultLastBlockInfo, error
 	if err != nil {
 		resJSON = []byte("<error: could not marshal last block info>")
 	}
-	return nil, fmt.Errorf("no block committed within the last %s (cutoff: %s), last block info: %s",
+	return nil, fmt.Errorf("no block committed within the last %s (cutoff: %s), current status: %s",
 		blockWithin, blockTimeThreshold.Format(time.RFC3339), string(resJSON))
 }
diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go
index e28e7d86..6732aa66 100644
--- a/rpc/tm/methods.go
+++ b/rpc/tm/methods.go
@@ -11,7 +11,7 @@ import (
 
 // Method names
 const (
-	// Status
+	// Status and healthcheck
 	Status  = "status"
 	NetInfo = "net_info"
 
@@ -44,16 +44,13 @@ const (
 	// Private keys and signing
 	GeneratePrivateAccount = "unsafe/gen_priv_account"
 	SignTx                 = "unsafe/sign_tx"
-
-	// Health check
-	LastBlockInfo = "last_block_info"
 )
 
 func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server.RPCFunc {
 	logger = logger.WithScope("GetRoutes")
 	return map[string]*server.RPCFunc{
 		// Status
-		Status:  server.NewRPCFunc(service.Status, ""),
+		Status:  server.NewRPCFunc(service.Status, "block_within"),
 		NetInfo: server.NewRPCFunc(service.NetInfo, ""),
 
 		// Accounts
@@ -85,7 +82,6 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server.
 
 		// Private account
 		GeneratePrivateAccount: server.NewRPCFunc(service.GeneratePrivateAccount, ""),
-		LastBlockInfo:          server.NewRPCFunc(service.LastBlockInfo, "block_within"),
 	}
 }
 
-- 
GitLab