diff --git a/blockchain/accept.go b/blockchain/accept.go
index d6d64832..cdd7b110 100644
--- a/blockchain/accept.go
+++ b/blockchain/accept.go
@@ -131,7 +131,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// blocks whose version is the serializedHeightVersion or
// newer once a majority of the network has upgraded. This is
// part of BIP0034.
- if blockHeader.Version >= serializedHeightVersion &&
+ if ShouldHaveSerializedBlockHeight(blockHeader) &&
b.isMajorityVersion(serializedHeightVersion, prevNode,
b.chainParams.BlockEnforceNumRequired) {
diff --git a/blockchain/validate.go b/blockchain/validate.go
index 8604b9ef..aa4f0796 100644
--- a/blockchain/validate.go
+++ b/blockchain/validate.go
@@ -89,10 +89,19 @@ func isNullOutpoint(outpoint *wire.OutPoint) bool {
return false
}
-// IsCoinBaseTx determines whether or not a transaction is a coinbase. A
-// coinbase is a special transaction created by miners that has no inputs. This
-// is represented in the block chain by a transaction with a single input that
-// has a previous output transaction index set to the maximum value along with a
+// ShouldHaveSerializedBlockHeight determines if a block should have a
+// serialized block height embedded within the scriptSig of its
+// coinbase transaction. Judgement is based on the block version in the block
+// header. Blocks with version 2 and above satisfy this criteria. See BIP0034
+// for further information.
+func ShouldHaveSerializedBlockHeight(header *wire.BlockHeader) bool {
+ return header.Version >= serializedHeightVersion
+}
+
+// IsCoinBaseTx determines whether or not a transaction is a coinbase. A coinbase
+// is a special transaction created by miners that has no inputs. This is
+// represented in the block chain by a transaction with a single input that has
+// a previous output transaction index set to the maximum value along with a
// zero hash.
//
// This function only differs from IsCoinBase in that it works with a raw wire
@@ -569,16 +578,18 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median
return checkBlockSanity(block, powLimit, timeSource, BFNone)
}
-// checkSerializedHeight checks if the signature script in the passed
-// transaction starts with the serialized block height of wantHeight.
-func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
+// ExtractCoinbaseHeight attempts to extract the height of the block
+// from the scriptSig of a coinbase transaction. Coinbase heights
+// are only present in blocks of version 2 or later. This was added as part of
+// BIP0034.
+func ExtractCoinbaseHeight(coinbaseTx *btcutil.Tx) (int64, error) {
sigScript := coinbaseTx.MsgTx().TxIn[0].SignatureScript
if len(sigScript) < 1 {
str := "the coinbase signature script for blocks of " +
"version %d or greater must start with the " +
"length of the serialized block height"
str = fmt.Sprintf(str, serializedHeightVersion)
- return ruleError(ErrMissingCoinbaseHeight, str)
+ return 0, ruleError(ErrMissingCoinbaseHeight, str)
}
serializedLen := int(sigScript[0])
@@ -587,19 +598,30 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
"version %d or greater must start with the " +
"serialized block height"
str = fmt.Sprintf(str, serializedLen)
- return ruleError(ErrMissingCoinbaseHeight, str)
+ return 0, ruleError(ErrMissingCoinbaseHeight, str)
}
serializedHeightBytes := make([]byte, 8, 8)
copy(serializedHeightBytes, sigScript[1:serializedLen+1])
serializedHeight := binary.LittleEndian.Uint64(serializedHeightBytes)
- if int64(serializedHeight) != wantHeight {
+
+ return int64(serializedHeight), nil
+}
+
+// checkSerializedHeight checks if the signature script in the passed
+// transaction starts with the serialized block height of wantHeight.
+func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
+ serializedHeight, err := ExtractCoinbaseHeight(coinbaseTx)
+ if err != nil {
+ return err
+ }
+
+ if serializedHeight != wantHeight {
str := fmt.Sprintf("the coinbase signature script serialized "+
"block height is %d when %d was expected",
serializedHeight, wantHeight)
return ruleError(ErrBadCoinbaseHeight, str)
}
-
return nil
}
diff --git a/blockmanager.go b/blockmanager.go
index 68d34bc2..cb72fe2f 100644
--- a/blockmanager.go
+++ b/blockmanager.go
@@ -587,8 +587,40 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
return
}
+ // Meta-data about the new block this peer is reporting. We use this
+ // below to update this peer's lastest block height and the heights of
+ // other peers based on their last announced block sha. This allows us
+ // to dynamically update the block heights of peers, avoiding stale heights
+ // when looking for a new sync peer. Upon acceptance of a block or
+ // recognition of an orphan, we also use this information to update
+ // the block heights over other peers who's invs may have been ignored
+ // if we are actively syncing while the chain is not yet current or
+ // who may have lost the lock announcment race.
+ var heightUpdate int32
+ var blkShaUpdate *wire.ShaHash
+
// Request the parents for the orphan block from the peer that sent it.
if isOrphan {
+ // We've just received an orphan block from a peer. In order
+ // to update the height of the peer, we try to extract the
+ // block height from the scriptSig of the coinbase transaction.
+ // Extraction is only attempted if the block's version is
+ // high enough (ver 2+).
+ header := &bmsg.block.MsgBlock().Header
+ if blockchain.ShouldHaveSerializedBlockHeight(header) {
+ coinbaseTx := bmsg.block.Transactions()[0]
+ cbHeight, err := blockchain.ExtractCoinbaseHeight(coinbaseTx)
+ if err != nil {
+ bmgrLog.Warnf("Unable to extract height from "+
+ "coinbase tx: %v", err)
+ } else {
+ bmgrLog.Debugf("Extracted height of %v from "+
+ "orphan block", cbHeight)
+ heightUpdate = int32(cbHeight)
+ blkShaUpdate = blockSha
+ }
+ }
+
orphanRoot := b.blockChain.GetOrphanRoot(blockSha)
locator, err := b.blockChain.LatestBlockLocator()
if err != nil {
@@ -600,7 +632,6 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
} else {
// When the block is not an orphan, log information about it and
// update the chain state.
-
b.progressLogger.LogBlockHeight(bmsg.block)
// Query the db for the latest best block since the block
@@ -609,6 +640,11 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
newestSha, newestHeight, _ := b.server.db.NewestSha()
b.updateChainState(newestSha, newestHeight)
+ // Update this peer's latest block height, for future
+ // potential sync node candidancy.
+ heightUpdate = int32(newestHeight)
+ blkShaUpdate = newestSha
+
// Allow any clients performing long polling via the
// getblocktemplate RPC to be notified when the new block causes
// their old block template to become stale.
@@ -618,6 +654,16 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
}
}
+ // Update the block height for this peer. But only send a message to
+ // the server for updating peer heights if this is an orphan or our
+ // chain is "current". This avoid sending a spammy amount of messages
+ // if we're syncing the chain from scratch.
+ if blkShaUpdate != nil && heightUpdate != 0 {
+ bmsg.peer.UpdateLastBlockHeight(heightUpdate)
+ if isOrphan || b.current() {
+ go b.server.UpdatePeerHeights(blkShaUpdate, int32(heightUpdate), bmsg.peer)
+ }
+ }
// Sync the db to disk.
b.server.db.Sync()
@@ -856,12 +902,6 @@ func (b *blockManager) haveInventory(invVect *wire.InvVect) (bool, error) {
// handleInvMsg handles inv messages from all peers.
// We examine the inventory advertised by the remote peer and act accordingly.
func (b *blockManager) handleInvMsg(imsg *invMsg) {
- // Ignore invs from peers that aren't the sync if we are not current.
- // Helps prevent fetching a mass of orphans.
- if imsg.peer != b.syncPeer && !b.current() {
- return
- }
-
// Attempt to find the final block in the inventory list. There may
// not be one.
lastBlock := -1
@@ -873,6 +913,36 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
}
}
+ // If this inv contains a block annoucement, and this isn't coming from
+ // our current sync peer or we're current, then update the last
+ // announced block for this peer. We'll use this information later to
+ // update the heights of peers based on blocks we've accepted that they
+ // previously announced.
+ if lastBlock != -1 && (imsg.peer != b.syncPeer || b.current()) {
+ imsg.peer.UpdateLastAnnouncedBlock(&invVects[lastBlock].Hash)
+ }
+
+ // Ignore invs from peers that aren't the sync if we are not current.
+ // Helps prevent fetching a mass of orphans.
+ if imsg.peer != b.syncPeer && !b.current() {
+ return
+ }
+
+ // If our chain is current and a peer announces a block we already
+ // know of, then update their current block height.
+ if lastBlock != -1 && b.current() {
+ exists, err := b.server.db.ExistsSha(&invVects[lastBlock].Hash)
+ if err == nil && exists {
+ blkHeight, err := b.server.db.FetchBlockHeightBySha(&invVects[lastBlock].Hash)
+ if err != nil {
+ bmgrLog.Warnf("Unable to fetch block height for block (sha: %v), %v",
+ &invVects[lastBlock].Hash, err)
+ } else {
+ imsg.peer.UpdateLastBlockHeight(int32(blkHeight))
+ }
+ }
+ }
+
// Request the advertised inventory if we don't already have it. Also,
// request parent blocks of orphans if we receive one we already have.
// Finally, attempt to detect potential stalls due to long side chains
diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md
index 8a7412d0..ac2ed4cd 100644
--- a/docs/json_rpc_api.md
+++ b/docs/json_rpc_api.md
@@ -393,8 +393,8 @@ the method name for further details such as parameter and return information.
|Method|getpeerinfo|
|Parameters|None|
|Description|Returns data about each connected network peer as an array of json objects.|
-|Returns|`[`
`{`
`"addr": "host:port", (string) the ip address and port of the peer`
`"services": "00000001", (string) the services supported by the peer`
`"lastrecv": n, (numeric) time the last message was received in seconds since 1 Jan 1970 GMT`
`"lastsend": n, (numeric) time the last message was sent in seconds since 1 Jan 1970 GMT`
`"bytessent": n, (numeric) total bytes sent`
`"bytesrecv": n, (numeric) total bytes received`
`"conntime": n, (numeric) time the connection was made in seconds since 1 Jan 1970 GMT`
`"pingtime": n, (numeric) number of microseconds the last ping took`
`"pingwait": n, (numeric) number of microseconds a queued ping has been waiting for a response`
`"version": n, (numeric) the protocol version of the peer`
`"subver": "useragent", (string) the user agent of the peer`
`"inbound": true_or_false, (boolean) whether or not the peer is an inbound connection`
`"startingheight": n, (numeric) the latest block height the peer knew about when the connection was established`
`"syncnode": true_or_false, (boolean) whether or not the peer is the sync peer`
`}, ...`
`]`|
-|Example Return|`[`
`{`
`"addr": "178.172.xxx.xxx:8333",`
`"services": "00000001",`
`"lastrecv": 1388183523,`
`"lastsend": 1388185470,`
`"bytessent": 287592965,`
`"bytesrecv": 780340,`
`"conntime": 1388182973,`
`"pingtime": 405551,`
`"pingwait": 183023,`
`"version": 70001,`
`"subver": "/btcd:0.4.0/",`
`"inbound": false,`
`"startingheight": 276921,`
`"syncnode": true,`
`}`
`]`|
+|Returns|`[`
`{`
`"addr": "host:port", (string) the ip address and port of the peer`
`"services": "00000001", (string) the services supported by the peer`
`"lastrecv": n, (numeric) time the last message was received in seconds since 1 Jan 1970 GMT`
`"lastsend": n, (numeric) time the last message was sent in seconds since 1 Jan 1970 GMT`
`"bytessent": n, (numeric) total bytes sent`
`"bytesrecv": n, (numeric) total bytes received`
`"conntime": n, (numeric) time the connection was made in seconds since 1 Jan 1970 GMT`
`"pingtime": n, (numeric) number of microseconds the last ping took`
`"pingwait": n, (numeric) number of microseconds a queued ping has been waiting for a response`
`"version": n, (numeric) the protocol version of the peer`
`"subver": "useragent", (string) the user agent of the peer`
`"inbound": true_or_false, (boolean) whether or not the peer is an inbound connection`
`"startingheight": n, (numeric) the latest block height the peer knew about when the connection was established`
`"currentheight": n, (numeric) the latest block height the peer is known to have relayed since connected`
`"syncnode": true_or_false, (boolean) whether or not the peer is the sync peer`
`}, ...`
`]`|
+|Example Return|`[`
`{`
`"addr": "178.172.xxx.xxx:8333",`
`"services": "00000001",`
`"lastrecv": 1388183523,`
`"lastsend": 1388185470,`
`"bytessent": 287592965,`
`"bytesrecv": 780340,`
`"conntime": 1388182973,`
`"pingtime": 405551,`
`"pingwait": 183023,`
`"version": 70001,`
`"subver": "/btcd:0.4.0/",`
`"inbound": false,`
`"startingheight": 276921,`
`"currentheight": 276955,`
`"syncnode": true,`
`}`
`]`|
[Return to Overview](#MethodOverview)
***
diff --git a/peer.go b/peer.go
index e9582c49..8b05f60f 100644
--- a/peer.go
+++ b/peer.go
@@ -191,7 +191,9 @@ type peer struct {
bytesReceived uint64
bytesSent uint64
userAgent string
+ startingHeight int32
lastBlock int32
+ lastAnnouncedBlock *wire.ShaHash
lastPingNonce uint64 // Set to nonce if we have a pending ping.
lastPingTime time.Time // Time we sent last ping.
lastPingMicros int64 // Time for last ping to return.
@@ -215,6 +217,27 @@ func (p *peer) isKnownInventory(invVect *wire.InvVect) bool {
return false
}
+// UpdateLastBlockHeight updates the last known block for the peer. It is safe
+// for concurrent access.
+func (p *peer) UpdateLastBlockHeight(newHeight int32) {
+ p.StatsMtx.Lock()
+ defer p.StatsMtx.Unlock()
+
+ peerLog.Tracef("Updating last block height of peer %v from %v to %v",
+ p.addr, p.lastBlock, newHeight)
+ p.lastBlock = int32(newHeight)
+}
+
+// UpdateLastAnnouncedBlock updates meta-data about the last block sha this
+// peer is known to have announced. It is safe for concurrent access.
+func (p *peer) UpdateLastAnnouncedBlock(blkSha *wire.ShaHash) {
+ p.StatsMtx.Lock()
+ defer p.StatsMtx.Unlock()
+
+ peerLog.Tracef("Updating last blk for peer %v, %v", p.addr, blkSha)
+ p.lastAnnouncedBlock = blkSha
+}
+
// AddKnownInventory adds the passed inventory to the cache of known inventory
// for the peer. It is safe for concurrent access.
func (p *peer) AddKnownInventory(invVect *wire.InvVect) {
@@ -400,6 +423,7 @@ func (p *peer) handleVersionMsg(msg *wire.MsgVersion) {
peerLog.Debugf("Negotiated protocol version %d for peer %s",
p.protocolVersion, p)
p.lastBlock = msg.LastBlock
+ p.startingHeight = msg.LastBlock
// Set the supported services for the peer to what the remote peer
// advertised.
diff --git a/server.go b/server.go
index c3e1e76f..a24a2817 100644
--- a/server.go
+++ b/server.go
@@ -5,6 +5,7 @@
package main
import (
+ "bytes"
"container/list"
"crypto/rand"
"encoding/binary"
@@ -70,6 +71,18 @@ type relayMsg struct {
data interface{}
}
+// updatePeerHeightsMsg is a message sent from the blockmanager to the server
+// after a new block has been accepted. The purpose of the message is to update
+// the heights of peers that were known to announce the block before we
+// connected it to the main chain or recognized it as an orphan. With these
+// updates, peer heights will be kept up to date, allowing for fresh data when
+// selecting sync peer candidacy.
+type updatePeerHeightsMsg struct {
+ newSha *wire.ShaHash
+ newHeight int32
+ originPeer *peer
+}
+
// server provides a bitcoin server for handling communications to and from
// bitcoin peers.
type server struct {
@@ -96,6 +109,7 @@ type server struct {
query chan interface{}
relayInv chan relayMsg
broadcast chan broadcastMsg
+ peerHeightsUpdate chan updatePeerHeightsMsg
wg sync.WaitGroup
quit chan struct{}
nat NAT
@@ -185,6 +199,35 @@ func (p *peerState) forAllPeers(closure func(p *peer)) {
p.forAllOutboundPeers(closure)
}
+// handleUpdatePeerHeight updates the heights of all peers who were known to
+// announce a block we recently accepted.
+func (s *server) handleUpdatePeerHeights(state *peerState, umsg updatePeerHeightsMsg) {
+ state.forAllPeers(func(p *peer) {
+ // The origin peer should already have the updated height.
+ if p == umsg.originPeer {
+ return
+ }
+
+ // Skip this peer if it hasn't recently announced any new blocks.
+ p.StatsMtx.Lock()
+ if p.lastAnnouncedBlock == nil {
+ p.StatsMtx.Unlock()
+ return
+ }
+
+ latestBlkSha := p.lastAnnouncedBlock.Bytes()
+ p.StatsMtx.Unlock()
+
+ // If the peer has recently announced a block, and this block
+ // matches our newly accepted block, then update their block
+ // height.
+ if bytes.Equal(latestBlkSha, umsg.newSha.Bytes()) {
+ p.UpdateLastBlockHeight(umsg.newHeight)
+ p.UpdateLastAnnouncedBlock(nil)
+ }
+ })
+}
+
// handleAddPeerMsg deals with adding new peers. It is invoked from the
// peerHandler goroutine.
func (s *server) handleAddPeerMsg(state *peerState, p *peer) bool {
@@ -414,7 +457,8 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) {
Version: p.protocolVersion,
SubVer: p.userAgent,
Inbound: p.inbound,
- StartingHeight: p.lastBlock,
+ StartingHeight: p.startingHeight,
+ CurrentHeight: p.lastBlock,
BanScore: 0,
SyncNode: p == syncPeer,
}
@@ -601,6 +645,10 @@ out:
case p := <-s.donePeers:
s.handleDonePeerMsg(state, p)
+ // Block accepted in mainchain or orphan, update peer height.
+ case umsg := <-s.peerHeightsUpdate:
+ s.handleUpdatePeerHeights(state, umsg)
+
// Peer to ban.
case p := <-s.banPeers:
s.handleBanPeerMsg(state, p)
@@ -818,6 +866,18 @@ func (s *server) NetTotals() (uint64, uint64) {
return s.bytesReceived, s.bytesSent
}
+// UpdatePeerHeights updates the heights of all peers who have have announced
+// the latest connected main chain block, or a recognized orphan. These height
+// updates allow us to dynamically refresh peer heights, ensuring sync peer
+// selection has access to the latest block heights for each peer.
+func (s *server) UpdatePeerHeights(latestBlkSha *wire.ShaHash, latestHeight int32, updateSource *peer) {
+ s.peerHeightsUpdate <- updatePeerHeightsMsg{
+ newSha: latestBlkSha,
+ newHeight: latestHeight,
+ originPeer: updateSource,
+ }
+}
+
// rebroadcastHandler keeps track of user submitted inventories that we have
// sent out but have not yet made it into a block. We periodically rebroadcast
// them in case our peers restarted or otherwise lost track of them.
@@ -1246,6 +1306,7 @@ func newServer(listenAddrs []string, db database.Db, chainParams *chaincfg.Param
broadcast: make(chan broadcastMsg, cfg.MaxPeers),
quit: make(chan struct{}),
modifyRebroadcastInv: make(chan interface{}),
+ peerHeightsUpdate: make(chan updatePeerHeightsMsg),
nat: nat,
db: db,
timeSource: blockchain.NewMedianTime(),