// PushGetBlocksMsg sends a getblocks message for the provided block locator // and stop hash. It will ignore back-to-back duplicate requests. func (p *peer) PushGetBlocksMsg(locator btcchain.BlockLocator, stopHash *btcwire.ShaHash) error { // Extract the begin hash from the block locator, if one was specified, // to use for filtering duplicate getblocks requests. // request. var beginHash *btcwire.ShaHash if len(locator) > 0 { beginHash = locator[0] } // Filter duplicate getblocks requests. if p.prevGetBlocksStop != nil && p.prevGetBlocksBegin != nil && beginHash != nil && stopHash.IsEqual(p.prevGetBlocksStop) && beginHash.IsEqual(p.prevGetBlocksBegin) { log.Tracef("PEER: Filtering duplicate [getblocks] with begin "+ "hash %v, stop hash %v", beginHash, stopHash) return nil } // Construct the getblocks request and queue it to be sent. msg := btcwire.NewMsgGetBlocks(stopHash) for _, hash := range locator { err := msg.AddBlockLocatorHash(hash) if err != nil { return err } } p.QueueMessage(msg, nil) // Update the previous getblocks request information for filtering // duplicates. p.prevGetBlocksBegin = beginHash p.prevGetBlocksStop = stopHash return nil }
// FetchTxUsedBySha returns the used/spent buffer for a given transaction. func (db *SqliteDb) FetchTxUsedBySha(txsha *btcwire.ShaHash) (spentbuf []byte, err error) { var row *sql.Row db.dbLock.Lock() defer db.dbLock.Unlock() rowBytes := txsha.String() txop := db.txop(txFetchUsedByShaStmt) row = txop.QueryRow(rowBytes) var databytes []byte err = row.Scan(&databytes) if err == sql.ErrNoRows { txop := db.txop(txtmpFetchUsedByShaStmt) row = txop.QueryRow(rowBytes) err = row.Scan(&databytes) if err == sql.ErrNoRows { err = btcdb.TxShaMissing return } if err != nil { log.Warnf("txtmp FetchLocationBySha: fail %v", err) return } } if err != nil { log.Warnf("FetchUsedBySha: fail %v", err) return } spentbuf = databytes return }
// fetchLocationBySha look up the Tx sha information by name. // Must be called with db lock held. func (db *SqliteDb) fetchLocationBySha(txsha *btcwire.ShaHash) (blockidx int64, txoff int, txlen int, err error) { var row *sql.Row var blockid int64 var ttxoff int var ttxlen int rowBytes := txsha.String() txop := db.txop(txFetchLocationByShaStmt) row = txop.QueryRow(rowBytes) err = row.Scan(&blockid, &ttxoff, &ttxlen) if err == sql.ErrNoRows { txop = db.txop(txtmpFetchLocationByShaStmt) row = txop.QueryRow(rowBytes) err = row.Scan(&blockid, &ttxoff, &ttxlen) if err == sql.ErrNoRows { err = btcdb.TxShaMissing return } if err != nil { log.Warnf("txtmp FetchLocationBySha: fail %v", err) return } } if err != nil { log.Warnf("FetchLocationBySha: fail %v", err) return } blockidx = blockid - 1 txoff = ttxoff txlen = ttxlen return }
// FetchHeightRange looks up a range of blocks by the start and ending // heights. Fetch is inclusive of the start height and exclusive of the // ending height. To fetch all hashes from the start height until no // more are present, use the special id `AllShas'. func (db *LevelDb) FetchHeightRange(startHeight, endHeight int64) (rshalist []btcwire.ShaHash, err error) { db.dbLock.Lock() defer db.dbLock.Unlock() var endidx int64 if endHeight == btcdb.AllShas { endidx = startHeight + 500 } else { endidx = endHeight } shalist := make([]btcwire.ShaHash, 0, endidx-startHeight) for height := startHeight; height < endidx; height++ { // TODO(drahn) fix blkFile from height key := int64ToKey(height) blkVal, lerr := db.lDb.Get(key, db.ro) if lerr != nil { break } var sha btcwire.ShaHash sha.SetBytes(blkVal[0:32]) shalist = append(shalist, sha) } if err != nil { return } //log.Tracef("FetchIdxRange idx %v %v returned %v shas err %v", startHeight, endHeight, len(shalist), err) return shalist, nil }
// RecordMinedTx searches through each account's TxStore, searching for a // sent transaction with the same txid as from a txmined notification. If // the transaction IDs match, the record in the TxStore is updated with // the full information about the newly-mined tx, and the TxStore is // scheduled to be written to disk.. func (am *AccountManager) RecordMinedTx(txid *btcwire.ShaHash, blkhash *btcwire.ShaHash, blkheight int32, blkindex int, blktime int64) error { for _, a := range am.AllAccounts() { // Search in reverse order. Since more recently-created // transactions are appended to the end of the store, it's // more likely to find it when searching from the end. for i := len(a.TxStore) - 1; i >= 0; i-- { sendtx, ok := a.TxStore[i].(*tx.SendTx) if ok { if bytes.Equal(txid.Bytes(), sendtx.TxID[:]) { copy(sendtx.BlockHash[:], blkhash.Bytes()) sendtx.BlockHeight = blkheight sendtx.BlockIndex = int32(blkindex) sendtx.BlockTime = blktime am.ds.ScheduleTxStoreWrite(a) return nil } } } } return errors.New("txid does not match any recorded sent transaction") }
func parsesha(argstr string) (argtype int, height int64, psha *btcwire.ShaHash, err error) { var sha btcwire.ShaHash var hashbuf string switch len(argstr) { case 64: hashbuf = argstr case 66: if argstr[0:2] != "0x" { log.Infof("prefix is %v", argstr[0:2]) err = ErrBadShaPrefix return } hashbuf = argstr[2:] default: if len(argstr) <= 16 { // assume value is height argtype = ArgHeight var h int h, err = strconv.Atoi(argstr) if err == nil { height = int64(h) return } log.Infof("Unable to parse height %v, err %v", height, err) } err = ErrBadShaLen return } var buf [32]byte for idx, ch := range hashbuf { var val rune switch { case ch >= '0' && ch <= '9': val = ch - '0' case ch >= 'a' && ch <= 'f': val = ch - 'a' + rune(10) case ch >= 'A' && ch <= 'F': val = ch - 'A' + rune(10) default: err = ErrBadShaChar return } b := buf[31-idx/2] if idx&1 == 1 { b |= byte(val) } else { b |= (byte(val) << 4) } buf[31-idx/2] = b } sha.SetBytes(buf[0:32]) psha = &sha return }
// Row returns row data for block iterator. func (bi *SqliteBlockIterator) Row() (key *btcwire.ShaHash, pver uint32, buf []byte, err error) { var keybytes []byte err = bi.rows.Scan(&keybytes, &pver, &buf) if err == nil { var retkey btcwire.ShaHash retkey.SetBytes(keybytes) key = &retkey } return }
// ShaHashToBig converts a btcwire.ShaHash into a big.Int that can be used to // perform math comparisons. func ShaHashToBig(hash *btcwire.ShaHash) *big.Int { // A ShaHash is in little-endian, but the big package wants the bytes // in big-endian. Reverse them. ShaHash.Bytes makes a copy, so it // is safe to modify the returned buffer. buf := hash.Bytes() blen := len(buf) for i := 0; i < blen/2; i++ { buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] } return new(big.Int).SetBytes(buf) }
// VerboseGetRawTransaction sends the verbose version of a getrawtransaction // request to receive details about a transaction. func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) { // NewGetRawTransactionCmd cannot fail with a single optarg. cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1) response := <-rpc.SendRequest(NewServerRequest(cmd)) var resultData btcjson.TxRawResult _, jsonErr := response.FinishUnmarshal(&resultData) if jsonErr != nil { return nil, jsonErr } return &resultData, nil }
// hashMerkleBranches takes two hashes, treated as the left and right tree // nodes, and returns the hash of their concatenation. This is a helper // function used to during generatation of a merkle tree. func hashMerkleBranches(left *btcwire.ShaHash, right *btcwire.ShaHash) *btcwire.ShaHash { // Concatenate the left and right nodes. var sha [btcwire.HashSize * 2]byte copy(sha[:btcwire.HashSize], left.Bytes()) copy(sha[btcwire.HashSize:], right.Bytes()) // Create a new sha hash from the double sha 256. Ignore the error // here since SetBytes can't fail here due to the fact DoubleSha256 // always returns a []byte of the right size regardless of input. newSha, _ := btcwire.NewShaHash(btcwire.DoubleSha256(sha[:])) return newSha }
func notifySpentData(n ntfnChan, txhash *btcwire.ShaHash, index uint32, spender *btcutil.Tx) { var buf bytes.Buffer // Ignore Serialize's error, as writing to a bytes.buffer // cannot fail. spender.MsgTx().Serialize(&buf) txStr := hex.EncodeToString(buf.Bytes()) ntfn := btcws.NewTxSpentNtfn(txhash.String(), int(index), txStr) n <- ntfn }
// GetBlockAsync returns an instance of a type that can be used to get the // result of the RPC at some future time by invoking the Receive function on the // returned instance. // // See GetBlock for the blocking version and more details. func (c *Client) GetBlockAsync(blockHash *btcwire.ShaHash) FutureGetBlockResult { hash := "" if blockHash != nil { hash = blockHash.String() } id := c.NextID() cmd, err := btcjson.NewGetBlockCmd(id, hash, false) if err != nil { return newFutureError(err) } return c.sendCmd(cmd) }
// GetBlockVerboseAsync returns an instance of a type that can be used to get // the result of the RPC at some future time by invoking the Receive function on // the returned instance. // // See GetBlockVerbose for the blocking version and more details. func (c *Client) GetBlockVerboseAsync(blockHash *btcwire.ShaHash, verboseTx bool) FutureGetBlockVerboseResult { hash := "" if blockHash != nil { hash = blockHash.String() } id := c.NextID() cmd, err := btcjson.NewGetBlockCmd(id, hash, true, verboseTx) if err != nil { return newFutureError(err) } return c.sendCmd(cmd) }
// fetchBlockShaByHeight returns a block hash based on its height in the // block chain. func (db *LevelDb) fetchBlockShaByHeight(height int64) (rsha *btcwire.ShaHash, err error) { key := int64ToKey(height) blkVal, err := db.lDb.Get(key, db.ro) if err != nil { log.Tracef("failed to find height %v", height) return // exists ??? } var sha btcwire.ShaHash sha.SetBytes(blkVal[0:32]) return &sha, nil }
func notifySpentData(wallet walletChan, txhash *btcwire.ShaHash, index uint32, spender *btcutil.Tx) { var buf bytes.Buffer // Ignore Serialize's error, as writing to a bytes.buffer // cannot fail. spender.MsgTx().Serialize(&buf) txStr := hex.EncodeToString(buf.Bytes()) // TODO(jrick): create a new notification in btcws and use that. ntfn := btcws.NewTxSpentNtfn(txhash.String(), int(index), txStr) mntfn, _ := ntfn.MarshalJSON() wallet <- mntfn }
// GetRawTransactionAsync returns an instance of a type that can be used to get // the result of the RPC at some future time by invoking the Receive function on // the returned instance. // // See GetRawTransaction for the blocking version and more details. func (c *Client) GetRawTransactionAsync(txHash *btcwire.ShaHash) FutureGetRawTransactionResult { hash := "" if txHash != nil { hash = txHash.String() } id := c.NextID() cmd, err := btcjson.NewGetRawTransactionCmd(id, hash, 0) if err != nil { return newFutureError(err) } return c.sendCmd(cmd) }
// insertTx inserts a tx hash and its associated data into the database. // Must be called with db lock held. func (db *SqliteDb) insertTx(txsha *btcwire.ShaHash, blockidx int64, txoff int, txlen int, usedbuf []byte) (err error) { tx := &db.txState if tx.tx == nil { err = db.startTx() if err != nil { return } } blockid := blockidx + 1 txd := tTxInsertData{txsha: txsha, blockid: blockid, txoff: txoff, txlen: txlen, usedbuf: usedbuf} log.Tracef("inserting tx %v for block %v off %v len %v", txsha, blockid, txoff, txlen) rowBytes := txsha.String() var op int // which table to insert data into. if db.UseTempTX { var tblockid int64 var ttxoff int var ttxlen int txop := db.txop(txFetchLocationByShaStmt) row := txop.QueryRow(rowBytes) err = row.Scan(&tblockid, &ttxoff, &ttxlen) if err != sql.ErrNoRows { // sha already present err = btcdb.DuplicateSha return } op = txtmpInsertStmt } else { op = txInsertStmt } txop := db.txop(op) _, err = txop.Exec(rowBytes, blockid, txoff, txlen, usedbuf) if err != nil { log.Warnf("failed to insert %v %v %v", txsha, blockid, err) return } if db.UseTempTX { db.TempTblSz++ } // put in insert list for replay tx.txInsertList = append(tx.txInsertList, txd) return }
func (db *LevelDb) setBlk(sha *btcwire.ShaHash, blkHeight int64, buf []byte) { // serialize var lw [8]byte binary.LittleEndian.PutUint64(lw[0:8], uint64(blkHeight)) shaKey := shaBlkToKey(sha) blkKey := int64ToKey(blkHeight) shaB := sha.Bytes() blkVal := make([]byte, len(shaB)+len(buf)) copy(blkVal[0:], shaB) copy(blkVal[len(shaB):], buf) db.lBatch().Put(shaKey, lw[:]) db.lBatch().Put(blkKey, blkVal) }
// blkExistsSha looks up the given block hash // returns true if it is present in the database. // CALLED WITH LOCK HELD func (db *SqliteDb) blkExistsSha(sha *btcwire.ShaHash) bool { var pver uint32 row := db.blkStmts[blkExistsSha].QueryRow(sha.Bytes()) err := row.Scan(&pver) if err == sql.ErrNoRows { return false } if err != nil { // ignore real errors? log.Warnf("blkExistsSha: fail %v", err) return false } return true }
// RescanEndBlockAsync returns an instance of a type that can be used to get // the result of the RPC at some future time by invoking the Receive function on // the returned instance. // // See RescanEndBlock for the blocking version and more details. // // NOTE: This is a btcd extension and requires a websocket connection. func (c *Client) RescanEndBlockAsync(startBlock *btcwire.ShaHash, addresses []btcutil.Address, outpoints []*btcwire.OutPoint, endBlock *btcwire.ShaHash) FutureRescanResult { // Not supported in HTTP POST mode. if c.config.HttpPostMode { return newFutureError(ErrNotificationsNotSupported) } // Ignore the notification if the client is not interested in // notifications. if c.ntfnHandlers == nil { return newNilFutureResult() } // Convert block hashes to strings. var startBlockShaStr, endBlockShaStr string if startBlock != nil { startBlockShaStr = startBlock.String() } if endBlock != nil { endBlockShaStr = endBlock.String() } // Convert addresses to strings. addrs := make([]string, 0, len(addresses)) for _, addr := range addresses { addrs = append(addrs, addr.String()) } // Convert outpoints. ops := make([]btcws.OutPoint, 0, len(outpoints)) for _, op := range outpoints { ops = append(ops, *btcws.NewOutPointFromWire(op)) } id := c.NextID() cmd, err := btcws.NewRescanCmd(id, startBlockShaStr, addrs, ops, endBlockShaStr) if err != nil { return newFutureError(err) } return c.sendCmd(cmd) }
// FetchBlockShaByHeight returns a block hash based on its height in the // block chain. func (db *SqliteDb) FetchBlockShaByHeight(height int64) (sha *btcwire.ShaHash, err error) { var row *sql.Row db.dbLock.Lock() defer db.dbLock.Unlock() blockidx := height + 1 // skew between btc blockid and sql row = db.blkStmts[blkFetchIdx].QueryRow(blockidx) var shabytes []byte err = row.Scan(&shabytes) if err != nil { return } var shaval btcwire.ShaHash shaval.SetBytes(shabytes) return &shaval, nil }
// insertSha stores a block hash and its associated data block with a // previous sha of `prevSha' and a version of `pver'. // insertSha shall be called with db lock held func (db *SqliteDb) insertBlockData(sha *btcwire.ShaHash, prevSha *btcwire.ShaHash, pver uint32, buf []byte) (blockid int64, err error) { tx := &db.txState if tx.tx == nil { err = db.startTx() if err != nil { return } } var prevOk bool var blkid int64 prevOk = db.blkExistsSha(prevSha) // exists -> ok if !prevOk { return 0, btcdb.PrevShaMissing } result, err := db.blkStmts[blkInsertSha].Exec(sha.Bytes(), pver, buf) if err != nil { return } blkid, err = result.LastInsertId() if err != nil { return 0, err } blkid -= 1 // skew between btc blockid and sql // Because we don't know know what the last idx is, we don't // cache unless already cached if db.lastBlkShaCached == true { db.lastBlkSha = *sha db.lastBlkIdx++ } bid := tBlockInsertData{*sha, pver, buf} tx.txInsertList = append(tx.txInsertList, bid) tx.txDataSz += len(buf) blockid = blkid return }
func (db *LevelDb) getBlkByHeight(blkHeight int64) (rsha *btcwire.ShaHash, rbuf []byte, err error) { var blkVal []byte key := int64ToKey(blkHeight) blkVal, err = db.lDb.Get(key, db.ro) if err != nil { log.Tracef("failed to find height %v", blkHeight) return // exists ??? } var sha btcwire.ShaHash sha.SetBytes(blkVal[0:32]) blockdata := make([]byte, len(blkVal[32:])) copy(blockdata[:], blkVal[32:]) return &sha, blockdata, nil }
// RecordMinedTx searches through each account's TxStore, searching for a // sent transaction with the same txid as from a txmined notification. If // the transaction IDs match, the record in the TxStore is updated with // the full information about the newly-mined tx, and the TxStore is // scheduled to be written to disk.. func (store *AccountStore) RecordMinedTx(txid *btcwire.ShaHash, blkhash *btcwire.ShaHash, blkheight int32, blkindex int, blktime int64) error { store.RLock() defer store.RUnlock() for _, account := range store.accounts { // The tx stores will be searched through while holding the // reader lock, and the writer will only be grabbed if necessary. account.TxStore.RLock() // Search in reverse order. Since more recently-created // transactions are appended to the end of the store, it's // more likely to find it when searching from the end. for i := len(account.TxStore.s) - 1; i >= 0; i-- { sendtx, ok := account.TxStore.s[i].(*tx.SendTx) if ok { if bytes.Equal(txid.Bytes(), sendtx.TxID[:]) { account.TxStore.RUnlock() account.TxStore.Lock() copy(sendtx.BlockHash[:], blkhash.Bytes()) sendtx.BlockHeight = blkheight sendtx.BlockIndex = int32(blkindex) sendtx.BlockTime = blktime account.TxStore.Unlock() account.ScheduleTxStoreWrite() return nil } } } account.TxStore.RUnlock() } return errors.New("txid does not match any recorded sent transaction") }
// fetchSha returns the datablock and pver for the given ShaHash. func (db *SqliteDb) fetchSha(sha btcwire.ShaHash) (buf []byte, pver uint32, blkid int64, err error) { db.dbLock.Lock() defer db.dbLock.Unlock() row := db.blkStmts[blkFetchSha].QueryRow(sha.Bytes()) var blockidx int64 var databytes []byte err = row.Scan(&pver, &databytes, &blockidx) if err == sql.ErrNoRows { return // no warning } if err != nil { log.Warnf("fail 2 %v", err) return } buf = databytes blkid = blockidx - 1 // skew between btc blockid and sql return }
// FetchHeightRange looks up a range of blocks by the start and ending // heights. Fetch is inclusive of the start height and exclusive of the // ending height. To fetch all hashes from the start height until no // more are present, use the special id `AllShas'. func (db *SqliteDb) FetchHeightRange(startHeight, endHeight int64) (rshalist []btcwire.ShaHash, err error) { db.dbLock.Lock() defer db.dbLock.Unlock() startidx := startHeight + 1 // skew between btc block height and sql var endidx int64 if endHeight == btcdb.AllShas { endidx = btcdb.AllShas // no skew if asking for all } else { endidx = endHeight + 1 // skew between btc block height and sql } rows, err := db.blkStmts[blkFetchIdxList].Query(startidx, endidx) if err != nil { log.Warnf("query failed %v", err) return } var shalist []btcwire.ShaHash for rows.Next() { var sha btcwire.ShaHash var shabytes []byte err = rows.Scan(&shabytes) if err != nil { log.Warnf("wtf? %v", err) break } sha.SetBytes(shabytes) shalist = append(shalist, sha) } rows.Close() if err == nil { rshalist = shalist } log.Tracef("FetchIdxRange idx %v %v returned %v shas err %v", startHeight, endHeight, len(shalist), err) return }
// createTxRawResult converts the passed transaction and associated parameters // to a raw transaction JSON object. func createTxRawResult(net btcwire.BitcoinNet, txSha string, mtx *btcwire.MsgTx, blk *btcutil.Block, maxidx int64, blksha *btcwire.ShaHash) (*btcjson.TxRawResult, error) { mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } vin, err := createVinList(mtx) if err != nil { return nil, err } vout, err := createVoutList(mtx, net) if err != nil { return nil, err } txReply := &btcjson.TxRawResult{ Hex: mtxHex, Txid: txSha, Vout: vout, Vin: vin, Version: mtx.Version, LockTime: mtx.LockTime, } if blk != nil { blockHeader := &blk.MsgBlock().Header idx := blk.Height() // This is not a typo, they are identical in bitcoind as well. txReply.Time = blockHeader.Timestamp.Unix() txReply.Blocktime = blockHeader.Timestamp.Unix() txReply.BlockHash = blksha.String() txReply.Confirmations = uint64(1 + maxidx - idx) } return txReply, nil }
// NewestSha provides an interface to quickly look up the sha of // the most recent (end) of the block chain. func (db *SqliteDb) NewestSha() (sha *btcwire.ShaHash, blkid int64, err error) { var row *sql.Row var blockidx int64 db.dbLock.Lock() defer db.dbLock.Unlock() // answer may be cached if db.lastBlkShaCached == true { shacopy := db.lastBlkSha sha = &shacopy blkid = db.lastBlkIdx - 1 // skew between btc blockid and sql return } querystr := "SELECT key, blockid FROM block ORDER BY blockid DESC;" tx := &db.txState if tx.tx != nil { row = tx.tx.QueryRow(querystr) } else { row = db.sqldb.QueryRow(querystr) } var shabytes []byte err = row.Scan(&shabytes, &blockidx) if err == nil { var retsha btcwire.ShaHash retsha.SetBytes(shabytes) sha = &retsha blkid = blockidx - 1 // skew between btc blockid and sql db.lastBlkSha = retsha db.lastBlkIdx = blockidx db.lastBlkShaCached = true } return }
func (db *LevelDb) setBlk(sha *btcwire.ShaHash, blkHeight int64, buf []byte) error { // serialize var lw bytes.Buffer err := binary.Write(&lw, binary.LittleEndian, blkHeight) if err != nil { err = fmt.Errorf("Write Fail") return err } shaKey := shaBlkToKey(sha) blkKey := int64ToKey(blkHeight) shaB := sha.Bytes() blkVal := make([]byte, len(shaB)+len(buf)) copy(blkVal[0:], shaB) copy(blkVal[len(shaB):], buf) db.lBatch().Put(shaKey, lw.Bytes()) db.lBatch().Put(blkKey, blkVal) return nil }
// BlockLocatorFromHash returns a block locator for the passed block hash. // See BlockLocator for details on the algotirhm used to create a block locator. // // In addition to the general algorithm referenced above, there are a couple of // special cases which are handled: // // - If the genesis hash is passed, there are no previous hashes to add and // therefore the block locator will only consist of the genesis hash // - If the passed hash is not currently known, the block locator will only // consist of the passed hash func (b *BlockChain) BlockLocatorFromHash(hash *btcwire.ShaHash) BlockLocator { // The locator contains the requested hash at the very least. locator := make(BlockLocator, 0, btcwire.MaxBlockLocatorsPerMsg) locator = append(locator, hash) // Nothing more to do if a locator for the genesis hash was requested. if hash.IsEqual(b.chainParams().GenesisHash) { return locator } // Attempt to find the height of the block that corresponds to the // passed hash, and if it's on a side chain, also find the height at // which it forks from the main chain. blockHeight := int64(-1) forkHeight := int64(-1) node, exists := b.index[*hash] if !exists { // Try to look up the height for passed block hash. Assume an // error means it doesn't exist and just return the locator for // the block itself. block, err := b.db.FetchBlockBySha(hash) if err != nil { return locator } blockHeight = block.Height() } else { blockHeight = node.height // Find the height at which this node forks from the main chain // if the node is on a side chain. if !node.inMainChain { for n := node; n.parent != nil; n = n.parent { if n.inMainChain { forkHeight = n.height break } } } } // Generate the block locators according to the algorithm described in // in the BlockLocator comment and make sure to leave room for the // final genesis hash. iterNode := node increment := int64(1) for len(locator) < btcwire.MaxBlockLocatorsPerMsg-1 { // Once there are 10 locators, exponentially increase the // distance between each block locator. if len(locator) > 10 { increment *= 2 } blockHeight -= increment if blockHeight < 1 { break } // As long as this is still on the side chain, walk backwards // along the side chain nodes to each block height. if forkHeight != -1 && blockHeight > forkHeight { // Intentionally use parent field instead of the // getPrevNodeFromNode function since we don't want to // dynamically load nodes when building block locators. // Side chain blocks should always be in memory already, // and if they aren't for some reason it's ok to skip // them. for iterNode != nil && blockHeight > iterNode.height { iterNode = iterNode.parent } if iterNode != nil && iterNode.height == blockHeight { locator = append(locator, iterNode.hash) } continue } // The desired block height is in the main chain, so look it up // from the main chain database. h, err := b.db.FetchBlockShaByHeight(blockHeight) if err != nil { // This shouldn't happen and it's ok to ignore block // locators, so just continue to the next one. log.Warnf("Lookup of known valid height failed %v", blockHeight) continue } locator = append(locator, h) } // Append the appropriate genesis block. locator = append(locator, b.chainParams().GenesisHash) return locator }