|
|
- package blocks
-
- import (
- db_ "github.com/tendermint/tendermint/db"
- "github.com/tendermint/tendermint/p2p"
- )
-
- const (
- blocksCh = "block"
-
- msgTypeUnknown = Byte(0x00)
- msgTypeState = Byte(0x01)
- msgTypeRequest = Byte(0x02)
- msgTypeData = Byte(0x03)
-
- dbKeyState = "state"
- )
-
- //-----------------------------------------------------------------------------
-
- // We request each item separately.
-
- const (
- dataTypeHeader = byte(0x01)
- dataTypeValidation = byte(0x02)
- dataTypeTxs = byte(0x03)
- )
-
- func _dataKey(dataType byte, height int) {
- switch dataType {
- case dataTypeHeader:
- return fmt.Sprintf("H%v", height)
- case dataTypeValidation:
- return fmt.Sprintf("V%v", height)
- case dataTypeTxs:
- return fmt.Sprintf("T%v", height)
- default:
- panic("Unknown datatype %X", dataType)
- }
- }
-
- func dataTypeFromObj(data interface{}) {
- switch data.(type) {
- case *Header:
- return dataTypeHeader
- case *Validation:
- return dataTypeValidation
- case *Txs:
- return dataTypeTxs
- default:
- panic("Unexpected datatype: %v", data)
- }
- }
-
- //-----------------------------------------------------------------------------
-
- // TODO: document
- type BlockManager struct {
- db *db_.LevelDB
- sw *p2p.Switch
- swEvents chan interface{}
- state blockManagerState
- dataStates map[string]*dataState // TODO: replace with CMap
- peerStates map[string]*peerState // TODO: replace with CMap
- quit chan struct{}
- started uint32
- stopped uint32
- }
-
- func NewBlockManager(sw *p2p.Switch, db *db_.LevelDB) *BlockManager {
- swEvents := make(chan interface{})
- sw.AddEventListener("BlockManager.swEvents", swEvents)
- bm := &BlockManager{
- db: db,
- sw: sw,
- swEvents: swEvents,
- dataStates: make(map[string]*dataState),
- peerStates: make(map[string]*peerState),
- quit: make(chan struct{}),
- }
- bm.loadState()
- return bm
- }
-
- func (bm *BlockManager) Start() {
- if atomic.CompareAndSwapUint32(&bm.started, 0, 1) {
- log.Info("Starting BlockManager")
- go bm.switchEventsHandler()
- }
- }
-
- func (bm *BlockManager) Stop() {
- if atomic.CompareAndSwapUint32(&bm.stopped, 0, 1) {
- log.Info("Stopping BlockManager")
- close(bm.quit)
- close(bm.swEvents)
- }
- }
-
- // NOTE: assumes that data is already validated.
- func (bm *BlockManager) StoreData(dataObj interface{}) {
- bm.mtx.Lock()
- defer bm.mtx.Unlock()
- dataType := dataTypeForObj(dataObj)
- dataKey := _dataKey(dataType, dataObj)
- // Update state
- // TODO
- // Remove dataState entry, we'll no longer request this.
- _dataState := bm.dataStates[dataKey]
- removedRequests := _dataState.removeRequestsForDataType(dataType)
- for _, request := range removedRequests {
- // TODO in future, notify peer that the request has been canceled.
- // No point doing this yet, requests in blocksCh are handled singlethreaded.
- }
- // What are we doing here?
- _peerState := bm.peerstates[dataKey]
-
- // If we have new data that extends our contiguous range, then announce it.
- }
-
- func (bm *BlockManager) LoadData(dataType byte, height int) interface{} {
- panic("not yet implemented")
- }
-
- func (bm *BlockManager) loadState() {
- // Load the state
- stateBytes := bm.db.Get(dbKeyState)
- if stateBytes == nil {
- log.Info("New BlockManager with no state")
- } else {
- err := json.Unmarshal(stateBytes, &bm.state)
- if err != nil {
- panic("Could not unmarshal state bytes: %X", stateBytes)
- }
- }
- }
-
- func (bm *BlockManager) saveState() {
- stateBytes, err := json.Marshal(&bm.state)
- if err != nil {
- panic("Could not marshal state bytes")
- }
- bm.db.Set(dbKeyState, stateBytes)
- }
-
- // Handle peer new/done events
- func (bm *BlockManager) switchEventsHandler() {
- for {
- swEvent, ok := <-bm.swEvents
- if !ok {
- break
- }
- switch swEvent.(type) {
- case p2p.SwitchEventNewPeer:
- event := swEvent.(p2p.SwitchEventNewPeer)
- // Create entry in .peerStates
- bm.peerStates[event.Peer.RemoteAddress().String()] = &peerState{}
- // Share our state with event.Peer
- msg := &stateMessage{
- lastHeaderHeight: bm.state.lastHeaderHeight,
- lastValidationHeight: bm.state.lastValidationHeight,
- lastTxsHeight: bm.state.lastTxsHeight,
- }
- tm := p2p.TypedMessage{msgTypeRequest, msg}
- event.Peer.TrySend(NewPacket(blocksCh, tm))
- case p2p.SwitchEventDonePeer:
- // Remove entry from .peerStates
- delete(bm.peerStates, event.Peer.RemoteAddress().String())
- default:
- log.Warning("Unhandled switch event type")
- }
- }
- }
-
- // Handle requests from the blocks channel
- func (bm *BlockManager) requestsHandler() {
- for {
- inPkt := bm.sw.Receive(blocksCh) // {Peer, Time, Packet}
- if inPkt == nil {
- // Client has stopped
- break
- }
-
- // decode message
- msg := decodeMessage(inPkt.Bytes)
- log.Info("requestHandler received %v", msg)
-
- switch msg.(type) {
- case *stateMessage:
- m := msg.(*stateMessage)
- peerState := bm.peerStates[inPkt.Peer.RemoteAddress.String()]
- if peerState == nil {
- continue // peer has since been disconnected.
- }
- peerState.applyStateMessage(m)
- // Consider requesting data.
- // 1. if has more validation and we want it
- // 2. if has more txs and we want it
- // if peerState.estimatedCredit() >= averageBlock
-
- // TODO: keep track of what we've requested from peer.
- // TODO: keep track of from which peers we've requested data.
- // TODO: be fair.
- case *requestMessage:
- // TODO: prevent abuse.
- case *dataMessage:
- // See if we want the data.
- // Validate data.
- // Add to db.
- // Update state & broadcast as necessary.
- default:
- // Ignore unknown message
- // bm.sw.StopPeerForError(inPkt.Peer, errInvalidMessage)
- }
- }
-
- // Cleanup
- }
-
- //-----------------------------------------------------------------------------
-
- // blockManagerState keeps track of which block parts are stored locally.
- // It's also persisted via JSON in the db.
- type blockManagerState struct {
- lastHeaderHeight uint64 // Last contiguous header height
- lastValidationHeight uint64 // Last contiguous validation height
- lastTxsHeight uint64 // Last contiguous txs height
- otherHeaderHeights []uint64
- otherValidationHeights []uint64
- otherTxsHeights []uint64
- }
-
- //-----------------------------------------------------------------------------
-
- // dataRequest keeps track of each request for a given peice of data & peer.
- type dataRequest struct {
- peer *p2p.Peer
- dataType byte
- height uint64
- time time.Time
- }
-
- //-----------------------------------------------------------------------------
-
- // dataState keeps track of all requests for a given piece of data.
- type dataState struct {
- mtx sync.Mutex
- requests []*dataRequest
- }
-
- func (ds *dataState) removeRequestsForDataType(dataType byte) []*dataRequest {
- ds.mtx.Lock()
- defer ds.mtx.Lock()
- requests := []*dataRequest{}
- filtered := []*dataRequest{}
- for _, request := range ds.requests {
- if request.dataType == dataType {
- filtered = append(filtered, request)
- } else {
- requests = append(requests, request)
- }
- }
- ds.requests = requests
- return filtered
- }
-
- //-----------------------------------------------------------------------------
-
- // XXX
- type peerState struct {
- mtx sync.Mutex
- lastHeaderHeight uint64 // Last contiguous header height
- lastValidationHeight uint64 // Last contiguous validation height
- lastTxsHeight uint64 // Last contiguous txs height
- dataBytesSent uint64 // Data bytes sent to peer
- dataBytesReceived uint64 // Data bytes received from peer
- numItemsReceived uint64 // Number of data items received
- numItemsUnreceived uint64 // Number of data items requested but not received
- numItemsSent uint64 // Number of data items sent
- requests map[string]*dataRequest
- }
-
- func (ps *peerState) applyStateMessage(msg *stateMessage) {
- ps.mtx.Lock()
- defer ps.mtx.Unlock()
- ps.lastHeaderHeight = msg.lastHeaderHeight
- ps.lastValidationHeight = msg.lastValidationHeight
- ps.lastTxsHeight = msg.lastTxsHeight
- }
-
- // Call this function for each data item received from peer, if the item was requested.
- // If the request timed out, dataBytesReceived is set to 0 to denote failure.
- func (ps *peerState) didReceiveData(dataKey string, dataBytesReceived uint64) {
- ps.mtx.Lock()
- defer ps.mtx.Lock()
- request := ps.requests[dataKey]
- if request == nil {
- log.Warning("Could not find peerState request with dataKey %v", dataKey)
- return
- }
- if dataBytesReceived == 0 {
- ps.numItemsUnreceived += 1
- } else {
- ps.dataBytesReceived += dataBytesReceived
- ps.numItemsReceived += 1
- }
- delete(ps.requests, dataKey)
- }
-
- // Call this function for each data item sent to peer, if the item was requested.
- func (ps *peerState) didSendData(dataKey string, dataBytesSent uint64) {
- ps.mtx.Lock()
- defer ps.mtx.Lock()
- if dataBytesSent == 0 {
- log.Warning("didSendData expects dataBytesSent > 0")
- return
- }
- ps.dataBytesSent += dataBytesSent
- ps.numItemsSent += 1
- }
-
- //-----------------------------------------------------------------------------
-
- /* Messages */
-
- // TODO: check for unnecessary extra bytes at the end.
- func decodeMessage(bz ByteSlice) (msg Message) {
- // log.Debug("decoding msg bytes: %X", bz)
- switch Byte(bz[0]) {
- case msgTypeState:
- return &stateMessage{}
- case msgTypeRequest:
- return readRequestMessage(bytes.NewReader(bz[1:]))
- case msgTypeData:
- return readDataMessage(bytes.NewReader(bz[1:]))
- default:
- return nil
- }
- }
-
- /*
- A stateMessage declares what (contiguous) blocks & headers are known.
- */
- type stateMessage struct {
- lastHeaderHeight uint64 // Last contiguous header height
- lastValidationHeight uint64 // Last contiguous validation height
- lastTxsHeight uint64 // Last contiguous txs height
- }
-
- func readStateMessage(r io.Reader) *stateMessage {
- lastHeaderHeight := ReadUInt64(r)
- lastValidationHeight := ReadUInt64(r)
- lastTxsHeight := ReadUInt64(r)
- return &stateMessage{
- lastHeaderHeight: lastHeaderHeight,
- lastValidationHeight: lastValidationHeight,
- lastTxsHeight: lastTxsHeight,
- }
- }
-
- func (m *stateMessage) WriteTo(w io.Writer) (n int64, err error) {
- n, err = WriteTo(msgTypeState, w, n, err)
- n, err = WriteTo(m.lastHeaderHeight, w, n, err)
- n, err = WriteTo(m.lastValidationHeight, w, n, err)
- n, err = WriteTo(m.lastTxsHeight, w, n, err)
- return
- }
-
- func (m *stateMessage) String() string {
- return fmt.Sprintf("[State %v/%v/%v]",
- m.lastHeaderHeight, m.lastValidationHeight, m.lastTxsHeight)
- }
-
- /*
- A requestMessage requests a block and/or header at a given height.
- */
- type requestMessage struct {
- dataType Byte
- height UInt64
- }
-
- func readRequestMessage(r io.Reader) *requestMessage {
- requestType := ReadByte(r)
- height := ReadUInt64(r)
- return &requestMessage{
- dataType: requestType,
- height: height,
- }
- }
-
- func (m *requestMessage) WriteTo(w io.Writer) (n int64, err error) {
- n, err = WriteTo(msgTypeRequest, w, n, err)
- n, err = WriteTo(m.dataType, w, n, err)
- n, err = WriteTo(m.height, w, n, err)
- return
- }
-
- func (m *requestMessage) String() string {
- return fmt.Sprintf("[Request %X@%v]", m.dataType, m.height)
- }
-
- /*
- A dataMessage contains block data, maybe requested.
- The data can be a Validation, Txs, or whole Block object.
- */
- type dataMessage struct {
- dataType Byte
- height UInt64
- bytes ByteSlice
- }
-
- func readDataMessage(r io.Reader) *dataMessage {
- dataType := ReadByte(r)
- height := ReadUInt64(r)
- bytes := ReadByteSlice(r)
- return &dataMessage{
- dataType: dataType,
- height: height,
- bytes: bytes,
- }
- }
-
- func (m *dataMessage) WriteTo(w io.Writer) (n int64, err error) {
- n, err = WriteTo(msgTypeData, w, n, err)
- n, err = WriteTo(m.dataType, w, n, err)
- n, err = WriteTo(m.height, w, n, err)
- n, err = WriteTo(m.bytes, w, n, err)
- return
- }
-
- func (m *dataMessage) String() string {
- return fmt.Sprintf("[Data %X@%v]", m.dataType, m.height)
- }
|