Initial import.
This commit is contained in:
parent
c59a86c467
commit
3f54e4199f
14 changed files with 3227 additions and 0 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
15
README.md
15
README.md
|
@ -2,3 +2,18 @@ btcd
|
||||||
====
|
====
|
||||||
|
|
||||||
btcd is an alternative full node bitcoin implementation written in Go (golang).
|
btcd is an alternative full node bitcoin implementation written in Go (golang).
|
||||||
|
|
||||||
|
This project is currently under active development and is not production ready
|
||||||
|
yet.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
The following is a list of major items remaining before production release:
|
||||||
|
|
||||||
|
- Implement multi-peer support
|
||||||
|
- Implement relay
|
||||||
|
- Complete address manager
|
||||||
|
- Rework the block syncing code to work with headers
|
||||||
|
- Documentation
|
||||||
|
- A lot of code cleanup
|
||||||
|
- Optimize
|
||||||
|
|
183
btcd/addrmanager.go
Normal file
183
btcd/addrmanager.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxAddresses identifies the maximum number of addresses that the
|
||||||
|
// address manager will track.
|
||||||
|
maxAddresses = 2500
|
||||||
|
newAddressBufferSize = 50
|
||||||
|
dumpAddressInterval = time.Minute * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateAddress is a helper function to either update an address already known
|
||||||
|
// to the address manager, or to add the address if not already known.
|
||||||
|
func updateAddress(a *AddrManager, netAddr *btcwire.NetAddress) {
|
||||||
|
// Protect concurrent access.
|
||||||
|
a.addrCacheLock.Lock()
|
||||||
|
defer a.addrCacheLock.Unlock()
|
||||||
|
|
||||||
|
// Update address if it already exists.
|
||||||
|
addr := NetAddressKey(netAddr)
|
||||||
|
if na, ok := a.addrCache[addr]; ok {
|
||||||
|
// Update the last seen time.
|
||||||
|
if netAddr.Timestamp.After(na.Timestamp) {
|
||||||
|
na.Timestamp = netAddr.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update services.
|
||||||
|
na.AddService(na.Services)
|
||||||
|
|
||||||
|
log.Tracef("[AMGR] Updated address manager address %s", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce max addresses.
|
||||||
|
if len(a.addrCache)+1 > maxAddresses {
|
||||||
|
log.Tracef("[AMGR] Max addresses of %d reached", maxAddresses)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.addrCache[addr] = netAddr
|
||||||
|
log.Tracef("[AMGR] Added new address %s for a total of %d addresses",
|
||||||
|
addr, len(a.addrCache))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrManager provides a concurrency safe address manager for caching potential
|
||||||
|
// peers on the bitcoin network.
|
||||||
|
type AddrManager struct {
|
||||||
|
addrCache map[string]*btcwire.NetAddress
|
||||||
|
addrCacheLock sync.Mutex
|
||||||
|
started bool
|
||||||
|
shutdown bool
|
||||||
|
newAddresses chan []*btcwire.NetAddress
|
||||||
|
removeAddresses chan []*btcwire.NetAddress
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// addressHandler is the main handler for the address manager. It must be run
|
||||||
|
// as a goroutine.
|
||||||
|
func (a *AddrManager) addressHandler() {
|
||||||
|
dumpAddressTicker := time.NewTicker(dumpAddressInterval)
|
||||||
|
out:
|
||||||
|
for !a.shutdown {
|
||||||
|
select {
|
||||||
|
case addrs := <-a.newAddresses:
|
||||||
|
for _, na := range addrs {
|
||||||
|
updateAddress(a, na)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-dumpAddressTicker.C:
|
||||||
|
if !a.shutdown {
|
||||||
|
// TODO: Dump addresses to database.
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-a.quit:
|
||||||
|
// TODO: Dump addresses to database.
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dumpAddressTicker.Stop()
|
||||||
|
a.wg.Done()
|
||||||
|
log.Trace("[AMGR] Address handler done")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins the core address handler which manages a pool of known
|
||||||
|
// addresses, timeouts, and interval based writes.
|
||||||
|
func (a *AddrManager) Start() {
|
||||||
|
// Already started?
|
||||||
|
if a.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("[AMGR] Starting address manager")
|
||||||
|
|
||||||
|
go a.addressHandler()
|
||||||
|
a.wg.Add(1)
|
||||||
|
a.started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully shuts down the address manager by stopping the main handler.
|
||||||
|
func (a *AddrManager) Stop() error {
|
||||||
|
if a.shutdown {
|
||||||
|
log.Warnf("[AMGR] Address manager is already in the process of " +
|
||||||
|
"shutting down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[AMGR] Address manager shutting down")
|
||||||
|
a.shutdown = true
|
||||||
|
a.quit <- true
|
||||||
|
a.wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddresses adds new addresses to the address manager. It enforces a max
|
||||||
|
// number of addresses and silently ignores duplicate addresses. It is
|
||||||
|
// safe for concurrent access.
|
||||||
|
func (a *AddrManager) AddAddresses(addrs []*btcwire.NetAddress) {
|
||||||
|
a.newAddresses <- addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddress adds a new address to the address manager. It enforces a max
|
||||||
|
// number of addresses and silently ignores duplicate addresses. It is
|
||||||
|
// safe for concurrent access.
|
||||||
|
func (a *AddrManager) AddAddress(addr *btcwire.NetAddress) {
|
||||||
|
addrs := []*btcwire.NetAddress{addr}
|
||||||
|
a.newAddresses <- addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedMoreAddresses returns whether or not the address manager needs more
|
||||||
|
// addresses.
|
||||||
|
func (a *AddrManager) NeedMoreAddresses() bool {
|
||||||
|
// Protect concurrent access.
|
||||||
|
a.addrCacheLock.Lock()
|
||||||
|
defer a.addrCacheLock.Unlock()
|
||||||
|
|
||||||
|
return len(a.addrCache)+1 <= maxAddresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumAddresses returns the number of addresses known to the address manager.
|
||||||
|
func (a *AddrManager) NumAddresses() int {
|
||||||
|
// Protect concurrent access.
|
||||||
|
a.addrCacheLock.Lock()
|
||||||
|
defer a.addrCacheLock.Unlock()
|
||||||
|
|
||||||
|
return len(a.addrCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressCache returns the current address cache. It must be treated as
|
||||||
|
// read-only.
|
||||||
|
func (a *AddrManager) AddressCache() map[string]*btcwire.NetAddress {
|
||||||
|
return a.addrCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new bitcoin address manager.
|
||||||
|
// Use Start to begin processing asynchronous address updates.
|
||||||
|
func NewAddrManager() *AddrManager {
|
||||||
|
am := AddrManager{
|
||||||
|
addrCache: make(map[string]*btcwire.NetAddress),
|
||||||
|
newAddresses: make(chan []*btcwire.NetAddress, newAddressBufferSize),
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
return &am
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetAddressKey returns a string key in the form of ip:port for IPv4 addresses
|
||||||
|
// or [ip]:port for IPv6 addresses.
|
||||||
|
func NetAddressKey(na *btcwire.NetAddress) string {
|
||||||
|
port := strconv.FormatUint(uint64(na.Port), 10)
|
||||||
|
addr := net.JoinHostPort(na.IP.String(), port)
|
||||||
|
return addr
|
||||||
|
}
|
110
btcd/addrmanager_test.go
Normal file
110
btcd/addrmanager_test.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"net"
|
||||||
|
"opensource.conformal.com/go/btcd-internal/addrmgr"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// naTest is used to describe a test to be perfomed against the NetAddressKey
|
||||||
|
// method.
|
||||||
|
type naTest struct {
|
||||||
|
in btcwire.NetAddress
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// naTests houses all of the tests to be performed against the NetAddressKey
|
||||||
|
// method.
|
||||||
|
var naTests = make([]naTest, 0)
|
||||||
|
|
||||||
|
func addNaTest(ip string, port uint16, want string) {
|
||||||
|
nip := net.ParseIP(ip)
|
||||||
|
na := btcwire.NetAddress{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: nip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
test := naTest{na, want}
|
||||||
|
naTests = append(naTests, test)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNaTests
|
||||||
|
func addNaTests() {
|
||||||
|
// IPv4
|
||||||
|
// Localhost
|
||||||
|
addNaTest("127.0.0.1", 8333, "127.0.0.1:8333")
|
||||||
|
addNaTest("127.0.0.1", 8334, "127.0.0.1:8334")
|
||||||
|
|
||||||
|
// Class A
|
||||||
|
addNaTest("1.0.0.1", 8333, "1.0.0.1:8333")
|
||||||
|
addNaTest("2.2.2.2", 8334, "2.2.2.2:8334")
|
||||||
|
addNaTest("27.253.252.251", 8335, "27.253.252.251:8335")
|
||||||
|
addNaTest("123.3.2.1", 8336, "123.3.2.1:8336")
|
||||||
|
|
||||||
|
// Private Class A
|
||||||
|
addNaTest("10.0.0.1", 8333, "10.0.0.1:8333")
|
||||||
|
addNaTest("10.1.1.1", 8334, "10.1.1.1:8334")
|
||||||
|
addNaTest("10.2.2.2", 8335, "10.2.2.2:8335")
|
||||||
|
addNaTest("10.10.10.10", 8336, "10.10.10.10:8336")
|
||||||
|
|
||||||
|
// Class B
|
||||||
|
addNaTest("128.0.0.1", 8333, "128.0.0.1:8333")
|
||||||
|
addNaTest("129.1.1.1", 8334, "129.1.1.1:8334")
|
||||||
|
addNaTest("180.2.2.2", 8335, "180.2.2.2:8335")
|
||||||
|
addNaTest("191.10.10.10", 8336, "191.10.10.10:8336")
|
||||||
|
|
||||||
|
// Private Class B
|
||||||
|
addNaTest("172.16.0.1", 8333, "172.16.0.1:8333")
|
||||||
|
addNaTest("172.16.1.1", 8334, "172.16.1.1:8334")
|
||||||
|
addNaTest("172.16.2.2", 8335, "172.16.2.2:8335")
|
||||||
|
addNaTest("172.16.172.172", 8336, "172.16.172.172:8336")
|
||||||
|
|
||||||
|
// Class C
|
||||||
|
addNaTest("193.0.0.1", 8333, "193.0.0.1:8333")
|
||||||
|
addNaTest("200.1.1.1", 8334, "200.1.1.1:8334")
|
||||||
|
addNaTest("205.2.2.2", 8335, "205.2.2.2:8335")
|
||||||
|
addNaTest("223.10.10.10", 8336, "223.10.10.10:8336")
|
||||||
|
|
||||||
|
// Private Class C
|
||||||
|
addNaTest("192.168.0.1", 8333, "192.168.0.1:8333")
|
||||||
|
addNaTest("192.168.1.1", 8334, "192.168.1.1:8334")
|
||||||
|
addNaTest("192.168.2.2", 8335, "192.168.2.2:8335")
|
||||||
|
addNaTest("192.168.192.192", 8336, "192.168.192.192:8336")
|
||||||
|
|
||||||
|
// IPv6
|
||||||
|
// Localhost
|
||||||
|
addNaTest("::1", 8333, "[::1]:8333")
|
||||||
|
addNaTest("fe80::1", 8334, "[fe80::1]:8334")
|
||||||
|
|
||||||
|
// Link-local
|
||||||
|
addNaTest("fe80::1:1", 8333, "[fe80::1:1]:8333")
|
||||||
|
addNaTest("fe91::2:2", 8334, "[fe91::2:2]:8334")
|
||||||
|
addNaTest("fea2::3:3", 8335, "[fea2::3:3]:8335")
|
||||||
|
addNaTest("feb3::4:4", 8336, "[feb3::4:4]:8336")
|
||||||
|
|
||||||
|
// Site-local
|
||||||
|
addNaTest("fec0::1:1", 8333, "[fec0::1:1]:8333")
|
||||||
|
addNaTest("fed1::2:2", 8334, "[fed1::2:2]:8334")
|
||||||
|
addNaTest("fee2::3:3", 8335, "[fee2::3:3]:8335")
|
||||||
|
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetAddressKey(t *testing.T) {
|
||||||
|
addNaTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(naTests))
|
||||||
|
for i, test := range naTests {
|
||||||
|
key := addrmgr.NetAddressKey(&test.in)
|
||||||
|
if key != test.want {
|
||||||
|
t.Errorf("NetAddressKey #%d\n got: %s want: %s", i, key, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
449
btcd/blockmanager.go
Normal file
449
btcd/blockmanager.go
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"github.com/conformal/btcchain"
|
||||||
|
"github.com/conformal/btcdb"
|
||||||
|
_ "github.com/conformal/btcdb/sqlite3"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
chanBufferSize = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
// inventoryItem is used to track known and requested inventory items.
|
||||||
|
type inventoryItem struct {
|
||||||
|
invVect *btcwire.InvVect
|
||||||
|
peers []*peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockMsg packages a bitcoin block message and the peer it came from together
|
||||||
|
// so the block handler has access to that information.
|
||||||
|
type blockMsg struct {
|
||||||
|
block *btcutil.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
// invMsg packages a bitcoin inv message and the peer it came from together
|
||||||
|
// so the block handler has access to that information.
|
||||||
|
type invMsg struct {
|
||||||
|
msg *btcwire.MsgInv
|
||||||
|
peer *peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// txMsg packages a bitcoin tx message and the peer it came from together
|
||||||
|
// so the block handler has access to that information.
|
||||||
|
type txMsg struct {
|
||||||
|
msg *btcwire.MsgTx
|
||||||
|
peer *peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockManager provides a concurrency safe block manager for handling all
|
||||||
|
// incoming block inventory advertisement as well as issuing requests to
|
||||||
|
// download needed blocks of the block chain from other peers. It works by
|
||||||
|
// forcing all incoming block inventory advertisements through a single
|
||||||
|
// goroutine which then determines whether the block is needed and how the
|
||||||
|
// requests should be made amongst multiple peers.
|
||||||
|
type blockManager struct {
|
||||||
|
server *server
|
||||||
|
started bool
|
||||||
|
shutdown bool
|
||||||
|
blockChain *btcchain.BlockChain
|
||||||
|
requestQueue *list.List
|
||||||
|
requestMap map[string]*inventoryItem
|
||||||
|
outstandingBlocks int
|
||||||
|
receivedLogBlocks int64
|
||||||
|
receivedLogTx int64
|
||||||
|
lastBlockLogTime time.Time
|
||||||
|
processingReqs bool
|
||||||
|
newBlocks chan bool
|
||||||
|
blockQueue chan *blockMsg
|
||||||
|
invQueue chan *invMsg
|
||||||
|
chainNotify chan *btcchain.Notification
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// logBlockHeight logs a new block height as an information message to show
|
||||||
|
// progress to the user. In order to prevent spam, it limits logging to one
|
||||||
|
// message every 10 seconds with duration and totals included.
|
||||||
|
func (b *blockManager) logBlockHeight(numTx, height int64) {
|
||||||
|
b.receivedLogBlocks++
|
||||||
|
b.receivedLogTx += numTx
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := now.Sub(b.lastBlockLogTime)
|
||||||
|
if b.outstandingBlocks != 0 && duration < time.Second*10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log information about new block height.
|
||||||
|
blockStr := "blocks"
|
||||||
|
if b.receivedLogBlocks == 1 {
|
||||||
|
blockStr = "block"
|
||||||
|
}
|
||||||
|
txStr := "transactions"
|
||||||
|
if b.receivedLogTx == 1 {
|
||||||
|
txStr = "transaction"
|
||||||
|
}
|
||||||
|
log.Infof("[BMGR] Processed %d %s (%d %s) in the last %s - Block "+
|
||||||
|
"height %d", b.receivedLogBlocks, blockStr, b.receivedLogTx,
|
||||||
|
txStr, duration, height)
|
||||||
|
|
||||||
|
b.receivedLogBlocks = 0
|
||||||
|
b.receivedLogTx = 0
|
||||||
|
b.lastBlockLogTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleInvMsg handles inventory messages for all peers. It adds blocks that
|
||||||
|
// we need along with which peers know about each block to a request queue
|
||||||
|
// based upon the advertised inventory. It also attempts to strike a balance
|
||||||
|
// between the number of in-flight blocks and keeping the request queue full
|
||||||
|
// by issuing more getblocks (MsgGetBlocks) requests as needed.
|
||||||
|
func (b *blockManager) handleInvMsg(msg *btcwire.MsgInv, p *peer) {
|
||||||
|
// Find the last block in the inventory list.
|
||||||
|
invVects := msg.InvList
|
||||||
|
var lastHash *btcwire.ShaHash
|
||||||
|
for i := len(invVects) - 1; i >= 0; i-- {
|
||||||
|
if invVects[i].Type == btcwire.InvVect_Block {
|
||||||
|
lastHash = &invVects[i].Hash
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range invVects {
|
||||||
|
switch iv.Type {
|
||||||
|
case btcwire.InvVect_Block:
|
||||||
|
// Ignore this block if we already have it.
|
||||||
|
// TODO(davec): Need to check orphans too.
|
||||||
|
if b.server.db.ExistsSha(&iv.Hash) {
|
||||||
|
log.Tracef("[BMGR] Ignoring known block %v.", &iv.Hash)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the peer to the list of peers which can serve the block if
|
||||||
|
// it's already queued to be fetched.
|
||||||
|
if item, ok := b.requestMap[iv.Hash.String()]; ok {
|
||||||
|
item.peers = append(item.peers, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item to the end of the request queue.
|
||||||
|
item := &inventoryItem{
|
||||||
|
invVect: iv,
|
||||||
|
peers: []*peer{p},
|
||||||
|
}
|
||||||
|
b.requestMap[item.invVect.Hash.String()] = item
|
||||||
|
b.requestQueue.PushBack(item)
|
||||||
|
b.outstandingBlocks++
|
||||||
|
|
||||||
|
case btcwire.InvVect_Tx:
|
||||||
|
// XXX: Handle transactions here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request more blocks if there aren't enough in-flight blocks.
|
||||||
|
if lastHash != nil && b.outstandingBlocks < btcwire.MaxBlocksPerMsg*5 {
|
||||||
|
stopHash := btcwire.ShaHash{}
|
||||||
|
gbmsg := btcwire.NewMsgGetBlocks(&stopHash)
|
||||||
|
gbmsg.AddBlockLocatorHash(lastHash)
|
||||||
|
p.QueueMessage(gbmsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBlockMsg handles block messages from all peers. It is currently
|
||||||
|
// very simple. It doesn't validate the block or handle orphans and side
|
||||||
|
// chains. It simply inserts the block into the database after ensuring the
|
||||||
|
// previous block is already inserted.
|
||||||
|
func (b *blockManager) handleBlockMsg(block *btcutil.Block) {
|
||||||
|
b.outstandingBlocks--
|
||||||
|
msg := block.MsgBlock()
|
||||||
|
|
||||||
|
// Process the block to include validation, best chain selection, orphan
|
||||||
|
// handling, etc.
|
||||||
|
err := b.blockChain.ProcessBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
blockSha, err2 := block.Sha()
|
||||||
|
if err2 != nil {
|
||||||
|
log.Errorf("[BMGR] %v", err2)
|
||||||
|
}
|
||||||
|
log.Warnf("[BMGR] Failed to process block %v: %v", blockSha, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log info about the new block height.
|
||||||
|
_, height, err := b.server.db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[BMGR] Failed to obtain latest sha - %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.logBlockHeight(int64(len(msg.Transactions)), height)
|
||||||
|
|
||||||
|
// Sync the db to disk when there are no more outstanding blocks.
|
||||||
|
// NOTE: Periodic syncs happen as new data is requested as well.
|
||||||
|
if b.outstandingBlocks <= 0 {
|
||||||
|
b.server.db.Sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockHandler is the main handler for the block manager. It must be run as a
|
||||||
|
// goroutine. It processes block and inv messages in a separate goroutine from
|
||||||
|
// the peer handlers so the block (MsgBlock) and tx (MsgTx) messages are handled
|
||||||
|
// by a single thread without needing to lock memory data structures. This is
|
||||||
|
// important because the block manager controls which blocks are needed and how
|
||||||
|
// the fetching should proceed.
|
||||||
|
//
|
||||||
|
// NOTE: Tx messages need to be handled here too.
|
||||||
|
// (either that or block and tx need to be handled in separate threads)
|
||||||
|
func (b *blockManager) blockHandler() {
|
||||||
|
out:
|
||||||
|
for !b.shutdown {
|
||||||
|
select {
|
||||||
|
// Handle new block messages.
|
||||||
|
case msg := <-b.blockQueue:
|
||||||
|
b.handleBlockMsg(msg.block)
|
||||||
|
|
||||||
|
// Handle new inventory messages.
|
||||||
|
case msg := <-b.invQueue:
|
||||||
|
b.handleInvMsg(msg.msg, msg.peer)
|
||||||
|
// Request the blocks.
|
||||||
|
if b.requestQueue.Len() > 0 && !b.processingReqs {
|
||||||
|
b.processingReqs = true
|
||||||
|
b.newBlocks <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-b.newBlocks:
|
||||||
|
numRequested := 0
|
||||||
|
gdmsg := btcwire.NewMsgGetData()
|
||||||
|
var p *peer
|
||||||
|
for e := b.requestQueue.Front(); e != nil; e = b.requestQueue.Front() {
|
||||||
|
item := e.Value.(*inventoryItem)
|
||||||
|
p = item.peers[0]
|
||||||
|
gdmsg.AddInvVect(item.invVect)
|
||||||
|
delete(b.requestMap, item.invVect.Hash.String())
|
||||||
|
b.requestQueue.Remove(e)
|
||||||
|
|
||||||
|
numRequested++
|
||||||
|
if numRequested >= btcwire.MaxInvPerMsg {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.server.db.Sync()
|
||||||
|
if len(gdmsg.InvList) > 0 && p != nil {
|
||||||
|
p.QueueMessage(gdmsg)
|
||||||
|
}
|
||||||
|
b.processingReqs = false
|
||||||
|
|
||||||
|
case <-b.quit:
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.wg.Done()
|
||||||
|
log.Trace("[BMGR] Block handler done")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleNotifyMsg handles notifications from btcchain. Currently it doesn't
|
||||||
|
// respond to any notifications, but the idea is that it requests missing blocks
|
||||||
|
// in response to orphan notifications and updates the wallet for blocks
|
||||||
|
// connected and disconnected to the main chain.
|
||||||
|
func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
|
||||||
|
switch notification.Type {
|
||||||
|
case btcchain.NTOrphanBlock:
|
||||||
|
// TODO(davec): Ask the peer to fill in the missing blocks for the
|
||||||
|
// orphan root if it's not nil.
|
||||||
|
orphanRoot := notification.Data.(*btcwire.ShaHash)
|
||||||
|
_ = orphanRoot
|
||||||
|
|
||||||
|
case btcchain.NTBlockAccepted:
|
||||||
|
// TODO(davec): Relay inventory, but don't relay old inventory
|
||||||
|
// during initial block download.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chainNotificationHandler is the handler for asynchronous notifications from
|
||||||
|
// btcchain. It must be run as a goroutine.
|
||||||
|
func (b *blockManager) chainNotificationHandler() {
|
||||||
|
out:
|
||||||
|
for !b.shutdown {
|
||||||
|
select {
|
||||||
|
case notification := <-b.chainNotify:
|
||||||
|
b.handleNotifyMsg(notification)
|
||||||
|
|
||||||
|
case <-b.quit:
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.wg.Done()
|
||||||
|
log.Trace("[BMGR] Chain notification handler done")
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueBlock adds the passed block message and peer to the block handling queue.
|
||||||
|
func (b *blockManager) QueueBlock(block *btcutil.Block) {
|
||||||
|
// Don't accept more blocks if we're shutting down.
|
||||||
|
if b.shutdown {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bmsg := blockMsg{block: block}
|
||||||
|
b.blockQueue <- &bmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueInv adds the passed inventory message and peer to the inventory handling
|
||||||
|
// queue.
|
||||||
|
func (b *blockManager) QueueInv(msg *btcwire.MsgInv, p *peer) {
|
||||||
|
// Don't accept more inventory if we're shutting down.
|
||||||
|
if b.shutdown {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imsg := invMsg{msg: msg, peer: p}
|
||||||
|
b.invQueue <- &imsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins the core block handler which processes block and inv messages.
|
||||||
|
func (b *blockManager) Start() {
|
||||||
|
// Already started?
|
||||||
|
if b.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("[BMGR] Starting block manager")
|
||||||
|
go b.blockHandler()
|
||||||
|
go b.chainNotificationHandler()
|
||||||
|
b.wg.Add(2)
|
||||||
|
b.started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully shuts down the block manager by stopping all asynchronous
|
||||||
|
// handlers and waiting for them to finish.
|
||||||
|
func (b *blockManager) Stop() error {
|
||||||
|
if b.shutdown {
|
||||||
|
log.Warnf("[BMGR] Block manager is already in the process of " +
|
||||||
|
"shutting down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[BMGR] Block manager shutting down")
|
||||||
|
b.shutdown = true
|
||||||
|
close(b.quit)
|
||||||
|
b.wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockLocators adds block locators to a getblocks message starting with
|
||||||
|
// the passed hash back to the genesis block hash. In order to keep the list
|
||||||
|
// of locator hashes to a reasonable number of entries, first it adds the
|
||||||
|
// most recent 10 block hashes (starting with the passed hash), then doubles the
|
||||||
|
// step each loop iteration to exponentially decrease the number of hashes the
|
||||||
|
// further away from head and closer to the genesis block it gets.
|
||||||
|
func (b *blockManager) AddBlockLocators(hash *btcwire.ShaHash, msg *btcwire.MsgGetBlocks) error {
|
||||||
|
// XXX(davec): This is fetching the block data too.
|
||||||
|
block, err := b.server.db.FetchBlockBySha(hash)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[BMGR] Lookup of known valid index failed %v", hash)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blockIndex := block.Height()
|
||||||
|
|
||||||
|
// We want inventory after the passed hash.
|
||||||
|
msg.AddBlockLocatorHash(hash)
|
||||||
|
|
||||||
|
// Generate the block locators according to the algorithm described in
|
||||||
|
// in the function comment and make sure to leave room for the already
|
||||||
|
// added hash and final genesis hash.
|
||||||
|
increment := int64(1)
|
||||||
|
for i := 1; i < btcwire.MaxBlockLocatorsPerMsg-2; i++ {
|
||||||
|
if i > 10 {
|
||||||
|
increment *= 2
|
||||||
|
}
|
||||||
|
blockIndex -= increment
|
||||||
|
if blockIndex <= 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := b.server.db.FetchBlockShaByHeight(blockIndex)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't happen and it's ok to ignore, so just
|
||||||
|
// continue to the next.
|
||||||
|
log.Warnf("[BMGR] Lookup of known valid index failed %v",
|
||||||
|
blockIndex)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(h)
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(&btcwire.GenesisHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBlockManager returns a new bitcoin block manager.
|
||||||
|
// Use Start to begin processing asynchronous block and inv updates.
|
||||||
|
func newBlockManager(s *server) *blockManager {
|
||||||
|
chainNotify := make(chan *btcchain.Notification, chanBufferSize)
|
||||||
|
bm := blockManager{
|
||||||
|
server: s,
|
||||||
|
blockChain: btcchain.New(s.db, s.btcnet, chainNotify),
|
||||||
|
requestQueue: list.New(),
|
||||||
|
requestMap: make(map[string]*inventoryItem),
|
||||||
|
lastBlockLogTime: time.Now(),
|
||||||
|
newBlocks: make(chan bool, 1),
|
||||||
|
blockQueue: make(chan *blockMsg, chanBufferSize),
|
||||||
|
invQueue: make(chan *invMsg, chanBufferSize),
|
||||||
|
chainNotify: chainNotify,
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
bm.blockChain.DisableVerify(cfg.VerifyDisabled)
|
||||||
|
return &bm
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBlockDB opens the block database and returns a handle to it.
|
||||||
|
func loadBlockDB() (btcdb.Db, error) {
|
||||||
|
dbPath := filepath.Join(cfg.DbDir, activeNetParams.dbName)
|
||||||
|
log.Infof("[BMGR] Loading block database from '%s'", dbPath)
|
||||||
|
db, err := btcdb.OpenDB("sqlite", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
// Return the error if it's not because the database doesn't
|
||||||
|
// exist.
|
||||||
|
if err != btcdb.DbDoesNotExist {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the db if it does not exist.
|
||||||
|
err = os.MkdirAll(cfg.DbDir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db, err = btcdb.CreateDB("sqlite", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the appropriate genesis block for the bitcoin network
|
||||||
|
// being connected to.
|
||||||
|
genesis := btcutil.NewBlock(activeNetParams.genesisBlock)
|
||||||
|
_, err := db.InsertBlock(genesis)
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Infof("[BMGR] Inserted genesis block %v",
|
||||||
|
activeNetParams.genesisHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest block height from the database.
|
||||||
|
_, height, err := db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Infof("[BMGR] Block database loaded with block height %d", height)
|
||||||
|
return db, nil
|
||||||
|
}
|
185
btcd/btcd.go
Normal file
185
btcd/btcd.go
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcchain"
|
||||||
|
"github.com/conformal/btcdb"
|
||||||
|
"github.com/conformal/btcscript"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/conformal/seelog"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const userAgent = "/btcd:0.0.1/"
|
||||||
|
|
||||||
|
// used by the dns seed code to pick a random last seen time
|
||||||
|
const (
|
||||||
|
secondsIn3Days int32 = 24 * 60 * 60 * 3
|
||||||
|
secondsIn4Days int32 = 24 * 60 * 60 * 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log seelog.LoggerInterface = seelog.Disabled
|
||||||
|
cfg *config
|
||||||
|
)
|
||||||
|
|
||||||
|
// newLogger creates a new seelog logger using the provided logging level and
|
||||||
|
// log message prefix.
|
||||||
|
func newLogger(level string, prefix string) seelog.LoggerInterface {
|
||||||
|
fmtstring := `
|
||||||
|
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000"
|
||||||
|
critmsgcount="500" minlevel="%s">
|
||||||
|
<outputs formatid="all">
|
||||||
|
<console/>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="all" format="[%%Time %%Date] [%%LEV] [%s] %%Msg%%n" />
|
||||||
|
</formats>
|
||||||
|
</seelog>`
|
||||||
|
config := fmt.Sprintf(fmtstring, level, prefix)
|
||||||
|
|
||||||
|
logger, err := seelog.LoggerFromConfigAsString(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to create logger: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// useLogger sets the btcd logger to the passed logger.
|
||||||
|
func useLogger(logger seelog.LoggerInterface) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLogLevel sets the log level for the logging system. It initialises a
|
||||||
|
// logger for each subsystem at the provided level.
|
||||||
|
func setLogLevel(logLevel string) []seelog.LoggerInterface {
|
||||||
|
var loggers []seelog.LoggerInterface
|
||||||
|
|
||||||
|
// Define sub-systems.
|
||||||
|
subSystems := []struct {
|
||||||
|
level string
|
||||||
|
prefix string
|
||||||
|
useLogger func(seelog.LoggerInterface)
|
||||||
|
}{
|
||||||
|
{logLevel, "BTCD", useLogger},
|
||||||
|
{logLevel, "BCDB", btcdb.UseLogger},
|
||||||
|
{logLevel, "CHAN", btcchain.UseLogger},
|
||||||
|
{logLevel, "SCRP", btcscript.UseLogger},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure all sub-systems with new loggers while keeping track of
|
||||||
|
// the created loggers to return so they can be flushed.
|
||||||
|
for _, s := range subSystems {
|
||||||
|
newLog := newLogger(s.level, s.prefix)
|
||||||
|
loggers = append(loggers, newLog)
|
||||||
|
s.useLogger(newLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggers
|
||||||
|
}
|
||||||
|
|
||||||
|
// btcdMain is the real main function for btcd. It is necessary to work around
|
||||||
|
// the fact that deferred functions do not run when os.Exit() is called.
|
||||||
|
func btcdMain() error {
|
||||||
|
// Initialize logging and setup deferred flushing to ensure all
|
||||||
|
// outstanding messages are written on shutdown.
|
||||||
|
loggers := setLogLevel(defaultLogLevel)
|
||||||
|
defer func() {
|
||||||
|
for _, logger := range loggers {
|
||||||
|
logger.Flush()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load configuration and parse command line.
|
||||||
|
tcfg, _, err := loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg = tcfg
|
||||||
|
|
||||||
|
// Change the logging level if needed.
|
||||||
|
if cfg.DebugLevel != defaultLogLevel {
|
||||||
|
loggers = setLogLevel(cfg.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the block database.
|
||||||
|
db, err := loadBlockDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Ensure the database is sync'd and closed on Ctrl+C.
|
||||||
|
addInterruptHandler(func() {
|
||||||
|
db.RollbackClose()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create server and start it.
|
||||||
|
listenAddr := net.JoinHostPort("", cfg.Port)
|
||||||
|
server, err := newServer(listenAddr, db, activeNetParams.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to start server on %v", listenAddr)
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
server.Start()
|
||||||
|
|
||||||
|
// only ask dns for peers if we don't have a list of initial seeds.
|
||||||
|
if !cfg.DisableDNSSeed {
|
||||||
|
// XXX need a proxy config entry
|
||||||
|
seedpeers := dnsDiscover(activeNetParams.dnsSeeds, "")
|
||||||
|
addresses := make([]*btcwire.NetAddress, len(seedpeers))
|
||||||
|
// if this errors then we have *real* problems
|
||||||
|
intPort, _ := strconv.Atoi(activeNetParams.peerPort)
|
||||||
|
for i, peer := range seedpeers {
|
||||||
|
addresses[i] = new(btcwire.NetAddress)
|
||||||
|
addresses[i].SetAddress(peer, uint16(intPort))
|
||||||
|
// bitcoind seeds with addresses from
|
||||||
|
// a time randomly selected between 3
|
||||||
|
// and 7 days ago.
|
||||||
|
addresses[i].Timestamp = time.Now().Add(-1 *
|
||||||
|
time.Second * time.Duration(secondsIn3Days+
|
||||||
|
rand.Int31n(secondsIn4Days)))
|
||||||
|
}
|
||||||
|
|
||||||
|
server.addrManager.AddAddresses(addresses)
|
||||||
|
// XXX if this is empty do we want to use hardcoded
|
||||||
|
// XXX peers like bitcoind does?
|
||||||
|
}
|
||||||
|
|
||||||
|
peers := cfg.ConnectPeers
|
||||||
|
if len(peers) == 0 {
|
||||||
|
peers = cfg.AddPeers
|
||||||
|
}
|
||||||
|
// Connect to initial peers.
|
||||||
|
for _, addr := range peers {
|
||||||
|
// Connect to peer and add it to the server.
|
||||||
|
server.ConnectPeerAsync(addr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.WaitForShutdown()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Use all processor cores.
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
// Work around defer not working after os.Exit()
|
||||||
|
err := btcdMain()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
271
btcd/config.go
Normal file
271
btcd/config.go
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"net"
|
||||||
|
"opensource.conformal.com/go/go-flags"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultConfigFilename = "btcd.conf"
|
||||||
|
defaultLogLevel = "info"
|
||||||
|
defaultBtcnet = btcwire.MainNet
|
||||||
|
defaultMaxPeers = 8
|
||||||
|
defaultBanDuration = time.Hour * 24
|
||||||
|
defaultVerifyEnabled = false
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultConfigFile = filepath.Join(btcdHomeDir(), defaultConfigFilename)
|
||||||
|
defaultDbDir = filepath.Join(btcdHomeDir(), "db")
|
||||||
|
)
|
||||||
|
|
||||||
|
// config defines the configuration options for btcd.
|
||||||
|
//
|
||||||
|
// See loadConfig for details on the configuration load process.
|
||||||
|
type config struct {
|
||||||
|
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
|
||||||
|
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
|
||||||
|
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
|
||||||
|
SeedPeer string `short:"s" long:"seedpeer" description:"Retrieve peer addresses from this peer and then disconnect"`
|
||||||
|
Port string `short:"p" long:"port" description:"Listen for connections on this port (default: 8333, testnet: 18333)"`
|
||||||
|
RpcPort string `short:"r" long:"rpcport" description:"Listen for json/rpc messages on this port"`
|
||||||
|
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
|
||||||
|
BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"`
|
||||||
|
VerifyDisabled bool `long:"noverify" description:"Disable block/transaction verification -- WARNING: This option can be dangerous and is for development use only"`
|
||||||
|
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||||
|
DbDir string `short:"b" long:"dbdir" description:"Directory to store database"`
|
||||||
|
RpcUser string `short:"u" long:"rpcuser" description:"Username for rpc connections"`
|
||||||
|
RpcPass string `short:"P" long:"rpcpass" description:"Password for rpc connections"`
|
||||||
|
DisableRpc bool `long:"norpc" description:"Disable built-in RPC server"`
|
||||||
|
DisableDNSSeed bool `long:"nodnsseed" description:"Disable DNS seeding for peers"`
|
||||||
|
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||||
|
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||||
|
func btcdHomeDir() string {
|
||||||
|
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||||
|
appData := os.Getenv("APPDATA")
|
||||||
|
if appData != "" {
|
||||||
|
return filepath.Join(appData, "btcd")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home != "" {
|
||||||
|
return filepath.Join(home, ".btcd")
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the worst case, use the current directory.
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// validLogLevel returns whether or not logLevel is a valid debug log level.
|
||||||
|
func validLogLevel(logLevel string) bool {
|
||||||
|
switch logLevel {
|
||||||
|
case "trace":
|
||||||
|
fallthrough
|
||||||
|
case "debug":
|
||||||
|
fallthrough
|
||||||
|
case "info":
|
||||||
|
fallthrough
|
||||||
|
case "warn":
|
||||||
|
fallthrough
|
||||||
|
case "error":
|
||||||
|
fallthrough
|
||||||
|
case "critical":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizePeerAddress returns addr with the default peer port appended if
|
||||||
|
// there is not already a port specified.
|
||||||
|
func normalizePeerAddress(addr string) string {
|
||||||
|
_, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return net.JoinHostPort(addr, activeNetParams.peerPort)
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeDuplicateAddresses returns a new slice with all duplicate entries in
|
||||||
|
// addrs removed.
|
||||||
|
func removeDuplicateAddresses(addrs []string) []string {
|
||||||
|
result := make([]string, 0)
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, val := range addrs {
|
||||||
|
if _, ok := seen[val]; !ok {
|
||||||
|
result = append(result, val)
|
||||||
|
seen[val] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeAndRemoveDuplicateAddresses(addrs []string) []string {
|
||||||
|
for i, addr := range addrs {
|
||||||
|
addrs[i] = normalizePeerAddress(addr)
|
||||||
|
}
|
||||||
|
addrs = removeDuplicateAddresses(addrs)
|
||||||
|
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateConfigWithActiveParams update the passed config with parameters
|
||||||
|
// from the active net params if the relevant options in the passed config
|
||||||
|
// object are the default so options specified by the user on the command line
|
||||||
|
// are not overridden.
|
||||||
|
func updateConfigWithActiveParams(cfg *config) {
|
||||||
|
if cfg.Port == netParams(defaultBtcnet).listenPort {
|
||||||
|
cfg.Port = activeNetParams.listenPort
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.RpcPort == netParams(defaultBtcnet).rpcPort {
|
||||||
|
cfg.RpcPort = activeNetParams.rpcPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesExists reports whether the named file or directory exists.
|
||||||
|
func fileExists(name string) bool {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfig initializes and parses the config using a config file and command
|
||||||
|
// line options.
|
||||||
|
//
|
||||||
|
// The configuration proceeds as follows:
|
||||||
|
// 1) Start with a default config with sane settings
|
||||||
|
// 2) Pre-parse the command line to check for an alternative config file
|
||||||
|
// 3) Load configuration file overwriting defaults with any specified options
|
||||||
|
// 4) Parse CLI options and overwrite/add any specified options
|
||||||
|
//
|
||||||
|
// The above results in btcd functioning properly without any config settings
|
||||||
|
// while still allowing the user to override settings with config files and
|
||||||
|
// command line options. Command line options always take precedence.
|
||||||
|
func loadConfig() (*config, []string, error) {
|
||||||
|
// Default config.
|
||||||
|
cfg := config{
|
||||||
|
DebugLevel: defaultLogLevel,
|
||||||
|
Port: netParams(defaultBtcnet).listenPort,
|
||||||
|
RpcPort: netParams(defaultBtcnet).rpcPort,
|
||||||
|
MaxPeers: defaultMaxPeers,
|
||||||
|
BanDuration: defaultBanDuration,
|
||||||
|
ConfigFile: defaultConfigFile,
|
||||||
|
DbDir: defaultDbDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A config file in the current directory takes precedence.
|
||||||
|
if fileExists(defaultConfigFilename) {
|
||||||
|
cfg.ConfigFile = defaultConfigFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-parse the command line options to see if an alternative config
|
||||||
|
// file was specified.
|
||||||
|
preCfg := cfg
|
||||||
|
preParser := flags.NewParser(&preCfg, flags.Default)
|
||||||
|
_, err := preParser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||||
|
preParser.WriteHelp(os.Stderr)
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load additional config from file.
|
||||||
|
parser := flags.NewParser(&cfg, flags.Default)
|
||||||
|
err = parser.ParseIniFile(preCfg.ConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
log.Warnf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line options again to ensure they take precedence.
|
||||||
|
remainingArgs, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The two test networks can't be selected simultaneously.
|
||||||
|
if cfg.TestNet3 && cfg.RegressionTest {
|
||||||
|
str := "%s: The testnet and regtest params can't be used " +
|
||||||
|
"together -- choose one of the two"
|
||||||
|
err := errors.New(fmt.Sprintf(str, "loadConfig"))
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose the active network params based on the testnet and regression
|
||||||
|
// test net flags.
|
||||||
|
if cfg.TestNet3 {
|
||||||
|
activeNetParams = netParams(btcwire.TestNet3)
|
||||||
|
} else if cfg.RegressionTest {
|
||||||
|
activeNetParams = netParams(btcwire.TestNet)
|
||||||
|
}
|
||||||
|
updateConfigWithActiveParams(&cfg)
|
||||||
|
|
||||||
|
// Validate debug log level.
|
||||||
|
if !validLogLevel(cfg.DebugLevel) {
|
||||||
|
str := "%s: The specified debug level is invalid -- parsed [%v]"
|
||||||
|
err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.DebugLevel))
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow ban durations that are too short.
|
||||||
|
if cfg.BanDuration < time.Duration(time.Second) {
|
||||||
|
str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
|
||||||
|
err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.BanDuration))
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --addPeer and --connect do not mix.
|
||||||
|
if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
|
||||||
|
str := "%s: the --addpeer and --connect options can not be " +
|
||||||
|
"mixed"
|
||||||
|
err := errors.New(fmt.Sprintf(str, "loadConfig"))
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect means no seeding or listening.
|
||||||
|
if len(cfg.ConnectPeers) > 0 {
|
||||||
|
cfg.DisableDNSSeed = true
|
||||||
|
// XXX turn off server listening.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default port to all added peer addresses if needed and remove
|
||||||
|
// duplicate addresses.
|
||||||
|
cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers)
|
||||||
|
cfg.ConnectPeers =
|
||||||
|
normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers)
|
||||||
|
|
||||||
|
return &cfg, remainingArgs, nil
|
||||||
|
}
|
171
btcd/discovery.go
Normal file
171
btcd/discovery.go
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
torSucceeded = 0x00
|
||||||
|
torGeneralError = 0x01
|
||||||
|
torNotAllowed = 0x02
|
||||||
|
torNetUnreachable = 0x03
|
||||||
|
torHostUnreachable = 0x04
|
||||||
|
torConnectionRefused = 0x05
|
||||||
|
torTtlExpired = 0x06
|
||||||
|
torCmdNotSupported = 0x07
|
||||||
|
torAddrNotSupported = 0x08
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTorInvalidAddressResponse = errors.New("Invalid address response")
|
||||||
|
ErrTorInvalidProxyResponse = errors.New("Invalid proxy response")
|
||||||
|
ErrTorUnrecognizedAuthMethod = errors.New("Invalid proxy authentication method")
|
||||||
|
|
||||||
|
torStatusErrors = map[byte]error{
|
||||||
|
torSucceeded: errors.New("Tor succeeded"),
|
||||||
|
torGeneralError: errors.New("Tor general error"),
|
||||||
|
torNotAllowed: errors.New("Tor not allowed"),
|
||||||
|
torNetUnreachable: errors.New("Tor network is unreachable"),
|
||||||
|
torHostUnreachable: errors.New("Tor host is unreachable"),
|
||||||
|
torConnectionRefused: errors.New("Tor connection refused"),
|
||||||
|
torTtlExpired: errors.New("Tor ttl expired"),
|
||||||
|
torCmdNotSupported: errors.New("Tor command not supported"),
|
||||||
|
torAddrNotSupported: errors.New("Tor address type not supported"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// try individual DNS server return list of strings for responses.
|
||||||
|
func doDNSLookup(host, proxy string) ([]net.IP, error) {
|
||||||
|
var err error
|
||||||
|
var addrs []net.IP
|
||||||
|
|
||||||
|
if proxy != "" {
|
||||||
|
addrs, err = torLookupIP(host, proxy)
|
||||||
|
} else {
|
||||||
|
addrs, err = net.LookupIP(host)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Tor to resolve DNS.
|
||||||
|
/*
|
||||||
|
TODO:
|
||||||
|
* this function must be documented internally
|
||||||
|
* this function does not handle IPv6
|
||||||
|
*/
|
||||||
|
func torLookupIP(host, proxy string) ([]net.IP, error) {
|
||||||
|
conn, err := net.Dial("tcp", proxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := []byte{'\x05', '\x01', '\x00'}
|
||||||
|
_, err = conn.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, 2)
|
||||||
|
_, err = conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if buf[0] != '\x05' {
|
||||||
|
return nil, ErrTorInvalidProxyResponse
|
||||||
|
}
|
||||||
|
if buf[1] != '\x00' {
|
||||||
|
return nil, ErrTorUnrecognizedAuthMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, 7+len(host))
|
||||||
|
buf[0] = 5 // protocol version
|
||||||
|
buf[1] = '\xF0' // Tor Resolve
|
||||||
|
buf[2] = 0 // reserved
|
||||||
|
buf[3] = 3 // Tor Resolve
|
||||||
|
buf[4] = byte(len(host))
|
||||||
|
copy(buf[5:], host)
|
||||||
|
buf[5+len(host)] = 0 // Port 0
|
||||||
|
|
||||||
|
_, err = conn.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, 4)
|
||||||
|
_, err = conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return nil, ErrTorInvalidProxyResponse
|
||||||
|
}
|
||||||
|
if buf[1] != 0 {
|
||||||
|
if int(buf[1]) > len(torStatusErrors) {
|
||||||
|
err = ErrTorInvalidProxyResponse
|
||||||
|
} else {
|
||||||
|
err := torStatusErrors[buf[1]]
|
||||||
|
if err == nil {
|
||||||
|
err = ErrTorInvalidProxyResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if buf[3] != 1 {
|
||||||
|
err := torStatusErrors[torGeneralError]
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, 4)
|
||||||
|
bytes, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if bytes != 4 {
|
||||||
|
return nil, ErrTorInvalidAddressResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
r := binary.BigEndian.Uint32(buf)
|
||||||
|
|
||||||
|
addr := make([]net.IP, 1)
|
||||||
|
addr[0] = net.IPv4(byte(r>>24), byte(r>>16), byte(r>>8), byte(r))
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsDiscover looks up the list of peers resolved by dns for all hosts in
|
||||||
|
// seeders. If proxy is not "" then this is used as a tor proxy for the
|
||||||
|
// resolution. If any errors occur then that seeder that errored will not have
|
||||||
|
// any hosts in the list. Therefore if all hosts failed an empty slice of
|
||||||
|
// strings will be returned.
|
||||||
|
func dnsDiscover(seeders []string, proxy string) []net.IP {
|
||||||
|
peers := []net.IP{}
|
||||||
|
for _, seeder := range seeders {
|
||||||
|
log.Debugf("[DISC] Fetching list of seeds from %v", seeder)
|
||||||
|
newPeers, err := doDNSLookup(seeder, proxy)
|
||||||
|
if err != nil {
|
||||||
|
seederPlusProxy := seeder
|
||||||
|
if proxy != "" {
|
||||||
|
seederPlusProxy = fmt.Sprintf("%s (proxy %s)",
|
||||||
|
seeder, proxy)
|
||||||
|
}
|
||||||
|
log.Warnf("[DISC] Failed to fetch dns seeds "+
|
||||||
|
"from %s: %v", seederPlusProxy, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
peers = append(peers, newPeers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peers
|
||||||
|
}
|
100
btcd/params.go
Normal file
100
btcd/params.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcchain"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// activeNetParams is a pointer to the parameters specific to the
|
||||||
|
// currently active bitcoin network.
|
||||||
|
var activeNetParams = netParams(defaultBtcnet)
|
||||||
|
|
||||||
|
// params is used to group parameters for various networks such as the main
|
||||||
|
// network and test networks.
|
||||||
|
type params struct {
|
||||||
|
dbName string
|
||||||
|
btcnet btcwire.BitcoinNet
|
||||||
|
genesisBlock *btcwire.MsgBlock
|
||||||
|
genesisHash *btcwire.ShaHash
|
||||||
|
powLimit *big.Int
|
||||||
|
powLimitBits uint32
|
||||||
|
peerPort string
|
||||||
|
listenPort string
|
||||||
|
rpcPort string
|
||||||
|
dnsSeeds []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// mainNetParams contains parameters specific to the main network
|
||||||
|
// (btcwire.MainNet).
|
||||||
|
var mainNetParams = params{
|
||||||
|
dbName: "btcd.db",
|
||||||
|
btcnet: btcwire.MainNet,
|
||||||
|
genesisBlock: btcchain.ChainParams(btcwire.MainNet).GenesisBlock,
|
||||||
|
genesisHash: btcchain.ChainParams(btcwire.MainNet).GenesisHash,
|
||||||
|
powLimit: btcchain.ChainParams(btcwire.MainNet).PowLimit,
|
||||||
|
powLimitBits: btcchain.ChainParams(btcwire.MainNet).PowLimitBits,
|
||||||
|
listenPort: btcwire.MainPort,
|
||||||
|
peerPort: btcwire.MainPort,
|
||||||
|
rpcPort: "8332",
|
||||||
|
dnsSeeds: []string{
|
||||||
|
"seed.bitcoin.sipa.be",
|
||||||
|
"dnsseed.bluematt.me",
|
||||||
|
"dnsseed.bitcoin.dashjr.org",
|
||||||
|
"bitseed.xf2.org",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// regressionParams contains parameters specific to the regression test network
|
||||||
|
// (btcwire.TestNet).
|
||||||
|
var regressionParams = params{
|
||||||
|
dbName: "btcd_regtest.db",
|
||||||
|
btcnet: btcwire.TestNet,
|
||||||
|
genesisBlock: btcchain.ChainParams(btcwire.TestNet).GenesisBlock,
|
||||||
|
genesisHash: btcchain.ChainParams(btcwire.TestNet).GenesisHash,
|
||||||
|
powLimit: btcchain.ChainParams(btcwire.TestNet).PowLimit,
|
||||||
|
powLimitBits: btcchain.ChainParams(btcwire.TestNet).PowLimitBits,
|
||||||
|
listenPort: btcwire.RegressionTestPort,
|
||||||
|
peerPort: btcwire.TestNetPort,
|
||||||
|
rpcPort: "18332",
|
||||||
|
dnsSeeds: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNet3Params contains parameters specific to the test network (version 3)
|
||||||
|
// (btcwire.TestNet3).
|
||||||
|
var testNet3Params = params{
|
||||||
|
dbName: "btcd_testnet.db",
|
||||||
|
btcnet: btcwire.TestNet3,
|
||||||
|
genesisBlock: btcchain.ChainParams(btcwire.TestNet3).GenesisBlock,
|
||||||
|
genesisHash: btcchain.ChainParams(btcwire.TestNet3).GenesisHash,
|
||||||
|
powLimit: btcchain.ChainParams(btcwire.TestNet3).PowLimit,
|
||||||
|
powLimitBits: btcchain.ChainParams(btcwire.TestNet3).PowLimitBits,
|
||||||
|
listenPort: btcwire.TestNetPort,
|
||||||
|
peerPort: btcwire.TestNetPort,
|
||||||
|
rpcPort: "18332",
|
||||||
|
dnsSeeds: []string{
|
||||||
|
"testnet-seed.bitcoin.petertodd.org",
|
||||||
|
"testnet-seed.bluematt.me",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// netParams returns parameters specific to the passed bitcoin network.
|
||||||
|
func netParams(btcnet btcwire.BitcoinNet) *params {
|
||||||
|
switch btcnet {
|
||||||
|
case btcwire.TestNet:
|
||||||
|
return ®ressionParams
|
||||||
|
|
||||||
|
case btcwire.TestNet3:
|
||||||
|
return &testNet3Params
|
||||||
|
|
||||||
|
// Return main net by default.
|
||||||
|
case btcwire.MainNet:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return &mainNetParams
|
||||||
|
}
|
||||||
|
}
|
752
btcd/peer.go
Normal file
752
btcd/peer.go
Normal file
|
@ -0,0 +1,752 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/conformal/btcdb"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"net"
|
||||||
|
"opensource.conformal.com/go/btcd-internal/addrmgr"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const outputBufferSize = 50
|
||||||
|
|
||||||
|
// zeroHash is the zero value hash (all zeros). It is defined as a convenience.
|
||||||
|
var zeroHash btcwire.ShaHash
|
||||||
|
|
||||||
|
// minUint32 is a helper function to return the minimum of two uint32s.
|
||||||
|
// This avoids a math import and the need to cast to floats.
|
||||||
|
func minUint32(a, b uint32) uint32 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer provides a bitcoin peer for handling bitcoin communications.
|
||||||
|
type peer struct {
|
||||||
|
server *server
|
||||||
|
protocolVersion uint32
|
||||||
|
btcnet btcwire.BitcoinNet
|
||||||
|
services btcwire.ServiceFlag
|
||||||
|
started bool
|
||||||
|
conn net.Conn
|
||||||
|
timeConnected time.Time
|
||||||
|
inbound bool
|
||||||
|
disconnect bool
|
||||||
|
persistent bool
|
||||||
|
versionKnown bool
|
||||||
|
knownAddresses map[string]bool
|
||||||
|
lastBlock int32
|
||||||
|
wg sync.WaitGroup
|
||||||
|
outputQueue chan btcwire.Message
|
||||||
|
quit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushVersionMsg sends a version message to the connected peer using the
|
||||||
|
// current state.
|
||||||
|
func (p *peer) pushVersionMsg() error {
|
||||||
|
_, blockNum, err := p.server.db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := btcwire.NewMsgVersionFromConn(p.conn, p.server.nonce,
|
||||||
|
userAgent, int32(blockNum))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: bitcoind appears to always enable the full node services flag
|
||||||
|
// of the remote peer netaddress field in the version message regardless
|
||||||
|
// of whether it knows it supports it or not. Also, bitcoind sets
|
||||||
|
// the services field of the local peer to 0 regardless of support.
|
||||||
|
//
|
||||||
|
// Realistically, this should be set as follows:
|
||||||
|
// - For outgoing connections:
|
||||||
|
// - Set the local netaddress services to what the local peer
|
||||||
|
// actually supports
|
||||||
|
// - Set the remote netaddress services to 0 to indicate no services
|
||||||
|
// as they are still unknown
|
||||||
|
// - For incoming connections:
|
||||||
|
// - Set the local netaddress services to what the local peer
|
||||||
|
// actually supports
|
||||||
|
// - Set the remote netaddress services to the what was advertised by
|
||||||
|
// by the remote peer in its version message
|
||||||
|
msg.AddrYou.Services = btcwire.SFNodeNetwork
|
||||||
|
|
||||||
|
// Advertise that we're a full node.
|
||||||
|
msg.Services = btcwire.SFNodeNetwork
|
||||||
|
|
||||||
|
p.outputQueue <- msg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleVersionMsg is invoked when a peer receives a version bitcoin message
|
||||||
|
// and is used to negotiate the protocol version details as well as kick start
|
||||||
|
// the communications.
|
||||||
|
func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) {
|
||||||
|
// Detect self connections.
|
||||||
|
if msg.Nonce == p.server.nonce {
|
||||||
|
log.Debugf("[PEER] Disconnecting peer connected to self %s",
|
||||||
|
p.conn.RemoteAddr())
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to one version message per peer.
|
||||||
|
if p.versionKnown {
|
||||||
|
log.Errorf("[PEER] Only one version message per peer is allowed %s.",
|
||||||
|
p.conn.RemoteAddr())
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negotiate the protocol version.
|
||||||
|
p.protocolVersion = minUint32(p.protocolVersion, uint32(msg.ProtocolVersion))
|
||||||
|
p.versionKnown = true
|
||||||
|
log.Debugf("[PEER] Negotiated protocol version %d for peer %s",
|
||||||
|
p.protocolVersion, p.conn.RemoteAddr())
|
||||||
|
p.lastBlock = msg.LastBlock
|
||||||
|
|
||||||
|
// Inbound connections.
|
||||||
|
if p.inbound {
|
||||||
|
// Set the supported services for the peer to what the remote
|
||||||
|
// peer advertised.
|
||||||
|
p.services = msg.Services
|
||||||
|
|
||||||
|
// Send version.
|
||||||
|
err := p.pushVersionMsg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add inbound peer address to the server address manager.
|
||||||
|
na, err := btcwire.NewNetAddress(p.conn.RemoteAddr(), p.services)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.server.addrManager.AddAddress(na)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send verack.
|
||||||
|
p.outputQueue <- btcwire.NewMsgVerAck()
|
||||||
|
|
||||||
|
// Outbound connections.
|
||||||
|
if !p.inbound {
|
||||||
|
// TODO: Only do this if we're listening, not doing the initial
|
||||||
|
// block download, and are routable.
|
||||||
|
// Advertise the local address.
|
||||||
|
na, err := btcwire.NewNetAddress(p.conn.LocalAddr(), p.services)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
na.Services = p.services
|
||||||
|
addresses := map[string]*btcwire.NetAddress{
|
||||||
|
addrmgr.NetAddressKey(na): na,
|
||||||
|
}
|
||||||
|
p.pushAddrMsg(addresses)
|
||||||
|
|
||||||
|
// Request known addresses if the server address manager needs
|
||||||
|
// more and the peer has a protocol version new enough to
|
||||||
|
// include a timestamp with addresses.
|
||||||
|
hasTimestamp := p.protocolVersion >= btcwire.NetAddressTimeVersion
|
||||||
|
if p.server.addrManager.NeedMoreAddresses() && hasTimestamp {
|
||||||
|
p.outputQueue <- btcwire.NewMsgGetAddr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request latest blocks if the peer has blocks we're interested in.
|
||||||
|
// XXX: Ask block manager for latest so we get in-flight too...
|
||||||
|
sha, lastBlock, err := p.server.db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
}
|
||||||
|
// If the peer has blocks we're interested in.
|
||||||
|
if p.lastBlock > int32(lastBlock) {
|
||||||
|
stopHash := btcwire.ShaHash{}
|
||||||
|
gbmsg := btcwire.NewMsgGetBlocks(&stopHash)
|
||||||
|
p.server.blockManager.AddBlockLocators(sha, gbmsg)
|
||||||
|
p.outputQueue <- gbmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Relay alerts.
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushTxMsg sends a tx message for the provided transaction hash to the
|
||||||
|
// connected peer. An error is returned if the transaction sha is not known.
|
||||||
|
func (p *peer) pushTxMsg(sha btcwire.ShaHash) error {
|
||||||
|
// We dont deal with these for now.
|
||||||
|
return errors.New("Tx fetching not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushBlockMsg sends a block message for the provided block hash to the
|
||||||
|
// connected peer. An error is returned if the block hash is not known.
|
||||||
|
func (p *peer) pushBlockMsg(sha btcwire.ShaHash) error {
|
||||||
|
// What should this function do about the rate limiting the
|
||||||
|
// number of blocks queued for this peer?
|
||||||
|
// Current thought is have a counting mutex in the peer
|
||||||
|
// such that if > N Tx/Block requests are currently in
|
||||||
|
// the tx queue, wait until the mutex clears allowing more to be
|
||||||
|
// sent. This prevents 500 1+MB blocks from being loaded into
|
||||||
|
// memory and sit around until the output queue drains.
|
||||||
|
// Actually the outputQueue has a limit of 50 in its queue
|
||||||
|
// but still 50MB to 1.6GB(50 32MB blocks) just setting
|
||||||
|
// in memory waiting to be sent is pointless.
|
||||||
|
// I would recommend a getdata request limit of about 5
|
||||||
|
// outstanding objects.
|
||||||
|
// Should the tx complete api be a mutex or channel?
|
||||||
|
|
||||||
|
blk, err := p.server.db.FetchBlockBySha(&sha)
|
||||||
|
if err != nil {
|
||||||
|
log.Tracef("[PEER] Unable to fetch requested block sha %v: %v",
|
||||||
|
&sha, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.QueueMessage(blk.MsgBlock())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetData is invoked when a peer receives a getdata bitcoin message and
|
||||||
|
// is used to deliver block and transaction information.
|
||||||
|
func (p *peer) handleGetDataMsg(msg *btcwire.MsgGetData) {
|
||||||
|
notFound := btcwire.NewMsgNotFound()
|
||||||
|
|
||||||
|
out:
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
var err error
|
||||||
|
switch iv.Type {
|
||||||
|
case btcwire.InvVect_Tx:
|
||||||
|
err = p.pushTxMsg(iv.Hash)
|
||||||
|
case btcwire.InvVect_Block:
|
||||||
|
err = p.pushBlockMsg(iv.Hash)
|
||||||
|
default:
|
||||||
|
log.Warnf("[PEER] Unknown type in inventory request %d",
|
||||||
|
iv.Type)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
notFound.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(notFound.InvList) != 0 {
|
||||||
|
p.QueueMessage(notFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetBlocksMsg is invoked when a peer receives a getdata bitcoin message.
|
||||||
|
func (p *peer) handleGetBlocksMsg(msg *btcwire.MsgGetBlocks) {
|
||||||
|
var err error
|
||||||
|
startIdx := int64(0)
|
||||||
|
endIdx := btcdb.AllShas
|
||||||
|
|
||||||
|
// Return all block hashes to the latest one (up to max per message) if
|
||||||
|
// no stop hash was specified.
|
||||||
|
// Attempt to find the ending index of the stop hash if specified.
|
||||||
|
if !msg.HashStop.IsEqual(&zeroHash) {
|
||||||
|
block, err := p.server.db.FetchBlockBySha(&msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
// Fetch all if we dont recognize the stop hash.
|
||||||
|
endIdx = btcdb.AllShas
|
||||||
|
}
|
||||||
|
endIdx = block.Height()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(davec): This should have some logic to utilize the additional
|
||||||
|
// locator hashes to ensure the proper chain.
|
||||||
|
for _, hash := range msg.BlockLocatorHashes {
|
||||||
|
// TODO(drahn) does using the caching interface make sense
|
||||||
|
// on index lookups ?
|
||||||
|
block, err := p.server.db.FetchBlockBySha(hash)
|
||||||
|
if err == nil {
|
||||||
|
// Start with the next hash since we know this one.
|
||||||
|
startIdx = block.Height() + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't attempt to fetch more than we can put into a single message.
|
||||||
|
if endIdx-startIdx > btcwire.MaxInvPerMsg {
|
||||||
|
endIdx = startIdx + btcwire.MaxInvPerMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the inventory from the block database.
|
||||||
|
hashList, err := p.server.db.FetchHeightRange(startIdx, endIdx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(" lookup returned %v ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to send.
|
||||||
|
if len(hashList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate inventory vectors and push the inventory message.
|
||||||
|
inv := btcwire.NewMsgInv()
|
||||||
|
for _, hash := range hashList {
|
||||||
|
iv := btcwire.InvVect{Type: btcwire.InvVect_Block, Hash: hash}
|
||||||
|
inv.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
p.QueueMessage(inv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetBlocksMsg is invoked when a peer receives a getheaders bitcoin
|
||||||
|
// message.
|
||||||
|
func (p *peer) handleGetHeadersMsg(msg *btcwire.MsgGetHeaders) {
|
||||||
|
var err error
|
||||||
|
startIdx := int64(0)
|
||||||
|
endIdx := btcdb.AllShas
|
||||||
|
|
||||||
|
// Return all block hashes to the latest one (up to max per message) if
|
||||||
|
// no stop hash was specified.
|
||||||
|
// Attempt to find the ending index of the stop hash if specified.
|
||||||
|
if !msg.HashStop.IsEqual(&zeroHash) {
|
||||||
|
block, err := p.server.db.FetchBlockBySha(&msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
// Fetch all if we dont recognize the stop hash.
|
||||||
|
endIdx = btcdb.AllShas
|
||||||
|
}
|
||||||
|
endIdx = block.Height()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(davec): This should have some logic to utilize the additional
|
||||||
|
// locator hashes to ensure the proper chain.
|
||||||
|
for _, hash := range msg.BlockLocatorHashes {
|
||||||
|
// TODO(drahn) does using the caching interface make sense
|
||||||
|
// on index lookups ?
|
||||||
|
block, err := p.server.db.FetchBlockBySha(hash)
|
||||||
|
if err == nil {
|
||||||
|
// Start with the next hash since we know this one.
|
||||||
|
startIdx = block.Height() + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't attempt to fetch more than we can put into a single message.
|
||||||
|
if endIdx-startIdx > btcwire.MaxBlockHeadersPerMsg {
|
||||||
|
endIdx = startIdx + btcwire.MaxBlockHeadersPerMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the inventory from the block database.
|
||||||
|
hashList, err := p.server.db.FetchHeightRange(startIdx, endIdx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("lookup returned %v ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to send.
|
||||||
|
if len(hashList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate inventory vectors and push the inventory message.
|
||||||
|
headersMsg := btcwire.NewMsgHeaders()
|
||||||
|
for _, hash := range hashList {
|
||||||
|
block, err := p.server.db.FetchBlockBySha(&hash)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[PEER] badness %v", err)
|
||||||
|
}
|
||||||
|
hdr := block.MsgBlock().Header // copy
|
||||||
|
hdr.TxnCount = 0
|
||||||
|
headersMsg.AddBlockHeader(&hdr)
|
||||||
|
}
|
||||||
|
p.QueueMessage(headersMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetAddrMsg is invoked when a peer receives a getaddr bitcoin message
|
||||||
|
// and is used to provide the peer with known addresses from the address
|
||||||
|
// manager.
|
||||||
|
func (p *peer) handleGetAddrMsg(msg *btcwire.MsgGetAddr) {
|
||||||
|
// Get the current known addresses from the address manager.
|
||||||
|
addrCache := p.server.addrManager.AddressCache()
|
||||||
|
|
||||||
|
// Push the addresses.
|
||||||
|
err := p.pushAddrMsg(addrCache)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushAddrMsg sends one, or more, addr message(s) to the connected peer using
|
||||||
|
// the provided addresses.
|
||||||
|
func (p *peer) pushAddrMsg(addresses map[string]*btcwire.NetAddress) error {
|
||||||
|
// Nothing to send.
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numAdded := 0
|
||||||
|
msg := btcwire.NewMsgAddr()
|
||||||
|
for _, na := range addresses {
|
||||||
|
// Filter addresses the peer already knows about.
|
||||||
|
if p.knownAddresses[addrmgr.NetAddressKey(na)] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the address to the message.
|
||||||
|
err := msg.AddAddress(na)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
numAdded++
|
||||||
|
|
||||||
|
// Split into multiple messages as needed.
|
||||||
|
if numAdded > 0 && numAdded%btcwire.MaxAddrPerMsg == 0 {
|
||||||
|
p.outputQueue <- msg
|
||||||
|
msg.ClearAddresses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message with remaining addresses if needed.
|
||||||
|
if numAdded%btcwire.MaxAddrPerMsg != 0 {
|
||||||
|
p.outputQueue <- msg
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAddrMsg is invoked when a peer receives an addr bitcoin message and
|
||||||
|
// is used to notify the server about advertised addresses.
|
||||||
|
func (p *peer) handleAddrMsg(msg *btcwire.MsgAddr) {
|
||||||
|
// Ignore old style addresses which don't include a timestamp.
|
||||||
|
if p.protocolVersion < btcwire.NetAddressTimeVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A message that has no addresses is invalid.
|
||||||
|
if len(msg.AddrList) == 0 {
|
||||||
|
log.Errorf("[PEER] Command [%s] from %s does not contain any addresses",
|
||||||
|
msg.Command(), p.conn.RemoteAddr())
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, na := range msg.AddrList {
|
||||||
|
// Don't add more address if we're disconnecting.
|
||||||
|
if p.disconnect {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the timestamp to 5 days ago if it's more than 24 hours
|
||||||
|
// in the future so this address is one of the first to be
|
||||||
|
// removed when space is needed.
|
||||||
|
now := time.Now()
|
||||||
|
if na.Timestamp.After(now.Add(time.Minute * 10)) {
|
||||||
|
na.Timestamp = now.Add(-1 * time.Hour * 24 * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add address to known addresses for this peer.
|
||||||
|
p.knownAddresses[addrmgr.NetAddressKey(na)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add addresses to server address manager. The address manager handles
|
||||||
|
// the details of things such as preventing duplicate addresses, max
|
||||||
|
// addresses, and last seen updates.
|
||||||
|
p.server.addrManager.AddAddresses(msg.AddrList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePingMsg is invoked when a peer receives a ping bitcoin message. For
|
||||||
|
// recent clients (protocol version > BIP0031Version), it replies with a pong
|
||||||
|
// message. For older clients, it does nothing and anything other than failure
|
||||||
|
// is considered a successful ping.
|
||||||
|
func (p *peer) handlePingMsg(msg *btcwire.MsgPing) {
|
||||||
|
// Only Reply with pong is message comes from a new enough client.
|
||||||
|
if p.protocolVersion > btcwire.BIP0031Version {
|
||||||
|
// Include nonce from ping so pong can be identified.
|
||||||
|
p.outputQueue <- btcwire.NewMsgPong(msg.Nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMessage reads the next bitcoin message from the peer with logging.
|
||||||
|
func (p *peer) readMessage() (msg btcwire.Message, buf []byte, err error) {
|
||||||
|
msg, buf, err = btcwire.ReadMessage(p.conn, p.protocolVersion, p.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("[PEER] Received command [%v] from %s", msg.Command(),
|
||||||
|
p.conn.RemoteAddr())
|
||||||
|
|
||||||
|
// Use closures to log expensive operations so they are only run when
|
||||||
|
// the logging level requires it.
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
return "[PEER] " + spew.Sdump(msg)
|
||||||
|
}))
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
return "[PEER] " + spew.Sdump(buf)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeMessage sends a bitcoin Message to the peer with logging.
|
||||||
|
func (p *peer) writeMessage(msg btcwire.Message) error {
|
||||||
|
log.Debugf("[PEER] Sending command [%v] to %s", msg.Command(),
|
||||||
|
p.conn.RemoteAddr())
|
||||||
|
|
||||||
|
// Use closures to log expensive operations so they are only run when the
|
||||||
|
// logging level requires it.
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
return "[PEER] msg" + spew.Sdump(msg)
|
||||||
|
}))
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.WriteMessage(&buf, msg, p.protocolVersion, p.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return "[PEER] " + spew.Sdump(buf.Bytes())
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Write the message to the peer.
|
||||||
|
err := btcwire.WriteMessage(p.conn, msg, p.protocolVersion, p.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAllowedByRegression returns whether or not the passed error is allowed by
|
||||||
|
// regression tests without disconnecting the peer. In particular, regression
|
||||||
|
// tests need to be allowed to send malformed messages without the peer being
|
||||||
|
// disconnected.
|
||||||
|
func (p *peer) isAllowedByRegression(err error) bool {
|
||||||
|
// Don't allow the error if it's not specifically a malformed message
|
||||||
|
// error.
|
||||||
|
if _, ok := err.(*btcwire.MessageError); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow the error if it's not coming from localhost or the
|
||||||
|
// hostname can't be determined for some reason.
|
||||||
|
host, _, err := net.SplitHostPort(p.conn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if host != "127.0.0.1" && host != "localhost" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed if all checks passed.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// inHandler handles all incoming messages for the peer. It must be run as a
|
||||||
|
// goroutine.
|
||||||
|
func (p *peer) inHandler() {
|
||||||
|
out:
|
||||||
|
for !p.disconnect {
|
||||||
|
rmsg, buf, err := p.readMessage()
|
||||||
|
if err != nil {
|
||||||
|
// In order to allow regression tests with malformed
|
||||||
|
// messages, don't disconnect the peer when we're in
|
||||||
|
// regression test mode and the error is one of the
|
||||||
|
// allowed errors.
|
||||||
|
if cfg.RegressionTest && p.isAllowedByRegression(err) {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only log the error if we're not forcibly disconnecting.
|
||||||
|
if !p.disconnect {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
}
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure version message comes first.
|
||||||
|
if _, ok := rmsg.(*btcwire.MsgVersion); !ok && !p.versionKnown {
|
||||||
|
log.Errorf("[PEER] A version message must precede all others")
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some messages are handled directly, while other messages
|
||||||
|
// are sent to a queue to be processed. Directly handling
|
||||||
|
// getdata and getblocks messages makes it impossible for a peer
|
||||||
|
// to spam with requests. However, it means that our getdata
|
||||||
|
// requests to it may not get prompt replies.
|
||||||
|
switch msg := rmsg.(type) {
|
||||||
|
case *btcwire.MsgVersion:
|
||||||
|
p.handleVersionMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgVerAck:
|
||||||
|
// Do nothing.
|
||||||
|
|
||||||
|
case *btcwire.MsgGetAddr:
|
||||||
|
p.handleGetAddrMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgAddr:
|
||||||
|
p.handleAddrMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgPing:
|
||||||
|
p.handlePingMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgPong:
|
||||||
|
// Don't do anything, but could try to work out network
|
||||||
|
// timing or similar.
|
||||||
|
|
||||||
|
case *btcwire.MsgAlert:
|
||||||
|
p.server.BroadcastMessage(msg, p)
|
||||||
|
|
||||||
|
case *btcwire.MsgBlock:
|
||||||
|
block := btcutil.NewBlockFromBlockAndBytes(msg, buf)
|
||||||
|
p.server.blockManager.QueueBlock(block)
|
||||||
|
|
||||||
|
case *btcwire.MsgInv:
|
||||||
|
p.server.blockManager.QueueInv(msg, p)
|
||||||
|
|
||||||
|
case *btcwire.MsgGetData:
|
||||||
|
p.handleGetDataMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgGetBlocks:
|
||||||
|
p.handleGetBlocksMsg(msg)
|
||||||
|
|
||||||
|
case *btcwire.MsgGetHeaders:
|
||||||
|
p.handleGetHeadersMsg(msg)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Debugf("[PEER] Received unhandled message of type %v: Fix Me",
|
||||||
|
rmsg.Command())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure connection is closed and notify server that the peer is done.
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
p.server.donePeers <- p
|
||||||
|
p.quit <- true
|
||||||
|
|
||||||
|
p.wg.Done()
|
||||||
|
log.Tracef("[PEER] Peer input handler done for %s", p.conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// outHandler handles all outgoing messages for the peer. It must be run as a
|
||||||
|
// goroutine. It uses a buffered channel to serialize output messages while
|
||||||
|
// allowing the sender to continue running asynchronously.
|
||||||
|
func (p *peer) outHandler() {
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-p.outputQueue:
|
||||||
|
// Don't send anything if we're disconnected.
|
||||||
|
if p.disconnect {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := p.writeMessage(msg)
|
||||||
|
if err != nil {
|
||||||
|
p.disconnect = true
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-p.quit:
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.wg.Done()
|
||||||
|
log.Tracef("[PEER] Peer output handler done for %s", p.conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueMessage adds the passed bitcoin message to the peer send queue. It
|
||||||
|
// uses a buffered channel to communicate with the output handler goroutine so
|
||||||
|
// it is automatically rate limited and safe for concurrent access.
|
||||||
|
func (p *peer) QueueMessage(msg btcwire.Message) {
|
||||||
|
p.outputQueue <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins processing input and output messages. It also sends the initial
|
||||||
|
// version message for outbound connections to start the negotiation process.
|
||||||
|
func (p *peer) Start() error {
|
||||||
|
// Already started?
|
||||||
|
if p.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("[PEER] Starting peer %s", p.conn.RemoteAddr())
|
||||||
|
|
||||||
|
// Send an initial version message if this is an outbound connection.
|
||||||
|
if !p.inbound {
|
||||||
|
err := p.pushVersionMsg()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[PEER] %v", err)
|
||||||
|
p.conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start processing input and output.
|
||||||
|
go p.inHandler()
|
||||||
|
go p.outHandler()
|
||||||
|
p.wg.Add(2)
|
||||||
|
p.started = true
|
||||||
|
|
||||||
|
// If server is shutting down, don't even start watchdog
|
||||||
|
if p.server.shutdown {
|
||||||
|
log.Debug("[PEER] server is shutting down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully shuts down the peer by signalling the async input and
|
||||||
|
// output handler and waiting for them to finish.
|
||||||
|
func (p *peer) Shutdown() {
|
||||||
|
log.Tracef("[PEER] Shutdown peer %s", p.conn.RemoteAddr())
|
||||||
|
p.disconnect = true
|
||||||
|
p.conn.Close()
|
||||||
|
p.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPeer returns a new bitcoin peer for the provided server and connection.
|
||||||
|
// Use start to begin processing incoming and outgoing messages.
|
||||||
|
func newPeer(s *server, conn net.Conn, inbound bool, persistent bool) *peer {
|
||||||
|
p := peer{
|
||||||
|
server: s,
|
||||||
|
protocolVersion: btcwire.ProtocolVersion,
|
||||||
|
btcnet: s.btcnet,
|
||||||
|
services: btcwire.SFNodeNetwork,
|
||||||
|
conn: conn,
|
||||||
|
timeConnected: time.Now(),
|
||||||
|
inbound: inbound,
|
||||||
|
persistent: persistent,
|
||||||
|
knownAddresses: make(map[string]bool),
|
||||||
|
outputQueue: make(chan btcwire.Message, outputBufferSize),
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
type logClosure func() string
|
||||||
|
|
||||||
|
func (c logClosure) String() string {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogClosure(c func() string) logClosure {
|
||||||
|
return logClosure(c)
|
||||||
|
}
|
486
btcd/rpcserver.go
Normal file
486
btcd/rpcserver.go
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/conformal/btcchain"
|
||||||
|
"github.com/conformal/btcjson"
|
||||||
|
"github.com/conformal/btcscript"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rpcServer holds the items the rpc server may need to access (config,
|
||||||
|
// shutdown, main server, etc.)
|
||||||
|
type rpcServer struct {
|
||||||
|
started bool
|
||||||
|
shutdown bool
|
||||||
|
server *server
|
||||||
|
wg sync.WaitGroup
|
||||||
|
rpcport string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
listener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start is used by server.go to start the rpc listener.
|
||||||
|
func (s *rpcServer) Start() {
|
||||||
|
if s.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("[RPCS] Starting RPC server")
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
jsonRpcRead(w, r, s)
|
||||||
|
})
|
||||||
|
listenAddr := net.JoinHostPort("", s.rpcport)
|
||||||
|
httpServer := &http.Server{Addr: listenAddr}
|
||||||
|
go func() {
|
||||||
|
log.Infof("[RPCS] RPC server listening on %s", s.listener.Addr())
|
||||||
|
httpServer.Serve(s.listener)
|
||||||
|
s.wg.Done()
|
||||||
|
}()
|
||||||
|
s.wg.Add(1)
|
||||||
|
s.started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop is used by server.go to stop the rpc listener.
|
||||||
|
func (s *rpcServer) Stop() error {
|
||||||
|
if s.shutdown {
|
||||||
|
log.Infof("[RPCS] RPC server is already in the process of shutting down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warnf("[RPCS] RPC server shutting down")
|
||||||
|
err := s.listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Problem shutting down rpc: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("[RPCS] RPC server shutdown complete")
|
||||||
|
s.wg.Wait()
|
||||||
|
s.shutdown = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRpcServer returns a new instance of the rpcServer struct.
|
||||||
|
func newRpcServer(s *server) (*rpcServer, error) {
|
||||||
|
rpc := rpcServer{
|
||||||
|
server: s,
|
||||||
|
}
|
||||||
|
// Get values from config
|
||||||
|
rpc.rpcport = cfg.RpcPort
|
||||||
|
rpc.username = cfg.RpcUser
|
||||||
|
rpc.password = cfg.RpcPass
|
||||||
|
|
||||||
|
listenAddr := net.JoinHostPort("", rpc.rpcport)
|
||||||
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Couldn't create listener: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rpc.listener = listener
|
||||||
|
return &rpc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonRpcRead is the main function that handles reading messages, getting
|
||||||
|
// the data the message requests, and writing the reply.
|
||||||
|
func jsonRpcRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
|
||||||
|
_ = spew.Dump
|
||||||
|
r.Close = true
|
||||||
|
if s.shutdown == true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rawReply btcjson.Reply
|
||||||
|
body, err := btcjson.GetRaw(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error getting json message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var message btcjson.Message
|
||||||
|
err = json.Unmarshal(body, &message)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error unmarshalling json message: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -32700,
|
||||||
|
Message: "Parse error",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: nil,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
msg, err := btcjson.MarshallAndSend(rawReply, w)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] received: %v", message)
|
||||||
|
|
||||||
|
// Deal with commands
|
||||||
|
switch message.Method {
|
||||||
|
case "getblockcount":
|
||||||
|
_, maxidx, _ := s.server.db.NewestSha()
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: maxidx,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
// btcd does not do mining so we can hardcode replies here.
|
||||||
|
case "getgenerate":
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: false,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
case "setgenerate":
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
case "gethashespersec":
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: 0,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
case "getblockhash":
|
||||||
|
var f interface{}
|
||||||
|
err = json.Unmarshal(body, &f)
|
||||||
|
m := f.(map[string]interface{})
|
||||||
|
var idx float64
|
||||||
|
for _, v := range m {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, u := range vv {
|
||||||
|
idx, _ = u.(float64)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sha, err := s.server.db.FetchBlockShaByHeight(int64(idx))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RCPS] Error getting block: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -1,
|
||||||
|
Message: "Block number out of range.",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: sha.String(),
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
case "getblock":
|
||||||
|
var f interface{}
|
||||||
|
err = json.Unmarshal(body, &f)
|
||||||
|
m := f.(map[string]interface{})
|
||||||
|
var hash string
|
||||||
|
for _, v := range m {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, u := range vv {
|
||||||
|
hash, _ = u.(string)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sha, err := btcwire.NewShaHashFromStr(hash)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error generating sha: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
blk, err := s.server.db.FetchBlockBySha(sha)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error fetching sha: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx := blk.Height()
|
||||||
|
buf, err := blk.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error fetching block: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
txList, _ := blk.TxShas()
|
||||||
|
|
||||||
|
txNames := make([]string, len(txList))
|
||||||
|
for i, v := range txList {
|
||||||
|
txNames[i] = v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, maxidx, err := s.server.db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Cannot get newest sha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHeader := &blk.MsgBlock().Header
|
||||||
|
blockReply := btcjson.BlockResult{
|
||||||
|
Hash: hash,
|
||||||
|
Version: blockHeader.Version,
|
||||||
|
MerkleRoot: blockHeader.MerkleRoot.String(),
|
||||||
|
PreviousHash: blockHeader.PrevBlock.String(),
|
||||||
|
Nonce: blockHeader.Nonce,
|
||||||
|
Time: blockHeader.Timestamp.Unix(),
|
||||||
|
Confirmations: uint64(1 + maxidx - idx),
|
||||||
|
Height: idx,
|
||||||
|
Tx: txNames,
|
||||||
|
Size: len(buf),
|
||||||
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
||||||
|
Difficulty: getDifficultyRatio(blockHeader.Bits),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next block unless we are already at the top.
|
||||||
|
if idx < maxidx {
|
||||||
|
shaNext, err := s.server.db.FetchBlockShaByHeight(int64(idx + 1))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] No next block: %v", err)
|
||||||
|
} else {
|
||||||
|
blockReply.NextHash = shaNext.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: blockReply,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
case "getrawtransaction":
|
||||||
|
var f interface{}
|
||||||
|
err = json.Unmarshal(body, &f)
|
||||||
|
m := f.(map[string]interface{})
|
||||||
|
var tx string
|
||||||
|
var verbose float64
|
||||||
|
for _, v := range m {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, u := range vv {
|
||||||
|
switch uu := u.(type) {
|
||||||
|
case string:
|
||||||
|
tx = uu
|
||||||
|
case float64:
|
||||||
|
verbose = uu
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(verbose) != 1 {
|
||||||
|
// Don't return details
|
||||||
|
// not used yet
|
||||||
|
} else {
|
||||||
|
txSha, _ := btcwire.NewShaHashFromStr(tx)
|
||||||
|
var txS *btcwire.MsgTx
|
||||||
|
txS, _, blksha, err := s.server.db.FetchTxBySha(txSha)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error fetching tx: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "No information available about transaction",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
blk, err := s.server.db.FetchBlockBySha(blksha)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error fetching sha: %v", err)
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
log.Tracef("[RPCS] reply: %v", rawReply)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx := blk.Height()
|
||||||
|
|
||||||
|
txOutList := txS.TxOut
|
||||||
|
voutList := make([]btcjson.Vout, len(txOutList))
|
||||||
|
|
||||||
|
txInList := txS.TxIn
|
||||||
|
vinList := make([]btcjson.Vin, len(txInList))
|
||||||
|
|
||||||
|
for i, v := range txInList {
|
||||||
|
vinList[i].Sequence = float64(v.Sequence)
|
||||||
|
disbuf, _ := btcscript.DisasmString(v.SignatureScript)
|
||||||
|
vinList[i].ScriptSig.Asm = strings.Replace(disbuf, " ", "", -1)
|
||||||
|
vinList[i].Vout = i + 1
|
||||||
|
log.Debugf(disbuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range txOutList {
|
||||||
|
voutList[i].N = i
|
||||||
|
voutList[i].Value = float64(v.Value) / 100000000
|
||||||
|
isbuf, _ := btcscript.DisasmString(v.PkScript)
|
||||||
|
voutList[i].ScriptPubKey.Asm = isbuf
|
||||||
|
voutList[i].ScriptPubKey.ReqSig = strings.Count(isbuf, "OP_CHECKSIG")
|
||||||
|
_, addr, err := btcscript.ScriptToAddress(v.PkScript)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Error getting address for %v: %v", txSha, err)
|
||||||
|
} else {
|
||||||
|
addrList := make([]string, 1)
|
||||||
|
addrList[0] = addr
|
||||||
|
voutList[i].ScriptPubKey.Addresses = addrList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, maxidx, err := s.server.db.NewestSha()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Cannot get newest sha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirmations := uint64(1 + maxidx - idx)
|
||||||
|
|
||||||
|
blockHeader := &blk.MsgBlock().Header
|
||||||
|
txReply := btcjson.TxRawResult{
|
||||||
|
Txid: tx,
|
||||||
|
Vout: voutList,
|
||||||
|
Vin: vinList,
|
||||||
|
Version: txS.Version,
|
||||||
|
LockTime: txS.LockTime,
|
||||||
|
// This is not a typo, they are identical in
|
||||||
|
// bitcoind as well.
|
||||||
|
Time: blockHeader.Timestamp.Unix(),
|
||||||
|
Blocktime: blockHeader.Timestamp.Unix(),
|
||||||
|
BlockHash: blksha.String(),
|
||||||
|
Confirmations: confirmations,
|
||||||
|
}
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: txReply,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "decoderawtransaction":
|
||||||
|
var f interface{}
|
||||||
|
err = json.Unmarshal(body, &f)
|
||||||
|
m := f.(map[string]interface{})
|
||||||
|
var hash string
|
||||||
|
for _, v := range m {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, u := range vv {
|
||||||
|
hash, _ = u.(string)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spew.Dump(hash)
|
||||||
|
txReply := btcjson.TxRawDecodeResult{}
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: txReply,
|
||||||
|
Error: nil,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -32601,
|
||||||
|
Message: "Method not found",
|
||||||
|
}
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: &message.Id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := btcjson.MarshallAndSend(rawReply, w)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDifficultyRatio returns the proof-of-work difficulty as a multiple of the
|
||||||
|
// minimum difficulty using the passed bits field from the header of a block.
|
||||||
|
func getDifficultyRatio(bits uint32) float64 {
|
||||||
|
// The minimum difficulty is the max possible proof-of-work limit bits
|
||||||
|
// converted back to a number. Note this is not the same as the the
|
||||||
|
// proof of work limit directly because the block difficulty is encoded
|
||||||
|
// in a block with the compact form which loses precision.
|
||||||
|
max := btcchain.CompactToBig(activeNetParams.powLimitBits)
|
||||||
|
target := btcchain.CompactToBig(bits)
|
||||||
|
|
||||||
|
difficulty := new(big.Rat).SetFrac(max, target)
|
||||||
|
outString := difficulty.FloatString(2)
|
||||||
|
diff, err := strconv.ParseFloat(outString, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[RPCS] Cannot get difficulty: %v", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
22
btcd/sample-btcd.conf
Normal file
22
btcd/sample-btcd.conf
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[Application Options]
|
||||||
|
; Debug logging level.
|
||||||
|
; Valid options are {trace, debug, info, warn, error, critical}
|
||||||
|
; debuglevel=info
|
||||||
|
|
||||||
|
; Use testnet.
|
||||||
|
; testnet=1
|
||||||
|
|
||||||
|
; Add as many specific space separated peers to connect to as desired.
|
||||||
|
; addpeer=192.168.1.1
|
||||||
|
; addpeer=10.0.0.2:8333
|
||||||
|
; addpeer=fe80::1
|
||||||
|
; addpeer=[fe80::2]:8333
|
||||||
|
; addpeer=192.168.1.1 10.0.0.2:8333 fe80::1 [fe80::2]:8333
|
||||||
|
|
||||||
|
; Maximum number of inbound and outbound peers.
|
||||||
|
; maxpeers=8
|
||||||
|
|
||||||
|
; How long to ban misbehaving peers. Valid time units are {s, m, h}.
|
||||||
|
; Minimum 1s.
|
||||||
|
; banduration=24h
|
||||||
|
; banduration=11h30m15s
|
434
btcd/server.go
Normal file
434
btcd/server.go
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"github.com/conformal/btcdb"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"net"
|
||||||
|
"opensource.conformal.com/go/btcd-internal/addrmgr"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedServices describes which services are supported by the server.
|
||||||
|
const supportedServices = btcwire.SFNodeNetwork
|
||||||
|
|
||||||
|
// connectionRetryInterval is the amount of time to wait in between retries
|
||||||
|
// when connecting to persistent peers.
|
||||||
|
const connectionRetryInterval = time.Second * 10
|
||||||
|
|
||||||
|
// directionString is a helper function that returns a string that represents
|
||||||
|
// the direction of a connection (inbound or outbound).
|
||||||
|
func directionString(inbound bool) string {
|
||||||
|
if inbound {
|
||||||
|
return "inbound"
|
||||||
|
}
|
||||||
|
return "outbound"
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcastMsg provides the ability to house a bitcoin message to be broadcast
|
||||||
|
// to all connected peers except specified excluded peers.
|
||||||
|
type broadcastMsg struct {
|
||||||
|
message btcwire.Message
|
||||||
|
excludePeers []*peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// server provides a bitcoin server for handling communications to and from
|
||||||
|
// bitcoin peers.
|
||||||
|
type server struct {
|
||||||
|
nonce uint64
|
||||||
|
listener net.Listener
|
||||||
|
btcnet btcwire.BitcoinNet
|
||||||
|
started bool
|
||||||
|
shutdown bool
|
||||||
|
shutdownSched bool
|
||||||
|
addrManager *addrmgr.AddrManager
|
||||||
|
rpcServer *rpcServer
|
||||||
|
blockManager *blockManager
|
||||||
|
newPeers chan *peer
|
||||||
|
donePeers chan *peer
|
||||||
|
banPeers chan *peer
|
||||||
|
broadcast chan broadcastMsg
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quit chan bool
|
||||||
|
db btcdb.Db
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAddPeerMsg deals with adding new peers. It is invoked from the
|
||||||
|
// peerHandler goroutine.
|
||||||
|
func (s *server) handleAddPeerMsg(peers *list.List, banned map[string]time.Time, p *peer) {
|
||||||
|
|
||||||
|
// Ignore new peers if we're shutting down.
|
||||||
|
direction := directionString(p.inbound)
|
||||||
|
if s.shutdown {
|
||||||
|
log.Infof("[SRVR] New peer %s (%s) ignored - server is "+
|
||||||
|
"shutting down", p.conn.RemoteAddr(), direction)
|
||||||
|
p.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect banned peers.
|
||||||
|
host, _, err := net.SplitHostPort(p.conn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[SRVR] %v", err)
|
||||||
|
p.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if banEnd, ok := banned[host]; ok {
|
||||||
|
if time.Now().Before(banEnd) {
|
||||||
|
log.Debugf("[SRVR] Peer %s is banned for another %v - "+
|
||||||
|
"disconnecting", host, banEnd.Sub(time.Now()))
|
||||||
|
p.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[SRVR] Peer %s is no longer banned", host)
|
||||||
|
delete(banned, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check for max peers from a single IP.
|
||||||
|
|
||||||
|
// Limit max number of total peers.
|
||||||
|
if peers.Len() >= cfg.MaxPeers {
|
||||||
|
log.Infof("[SRVR] Max peers reached [%d] - disconnecting "+
|
||||||
|
"peer %s (%s)", cfg.MaxPeers, p.conn.RemoteAddr(),
|
||||||
|
direction)
|
||||||
|
p.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new peer and start it.
|
||||||
|
log.Infof("[SRVR] New peer %s (%s)", p.conn.RemoteAddr(), direction)
|
||||||
|
peers.PushBack(p)
|
||||||
|
p.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDonePeerMsg deals with peers that have signalled they are done. It is
|
||||||
|
// invoked from the peerHandler goroutine.
|
||||||
|
func (s *server) handleDonePeerMsg(peers *list.List, p *peer) {
|
||||||
|
direction := directionString(p.inbound)
|
||||||
|
for e := peers.Front(); e != nil; e = e.Next() {
|
||||||
|
if e.Value == p {
|
||||||
|
peers.Remove(e)
|
||||||
|
log.Infof("[SRVR] Removed peer %s (%s)",
|
||||||
|
p.conn.RemoteAddr(), direction)
|
||||||
|
|
||||||
|
// Issue an asynchronous reconnect if the peer was a
|
||||||
|
// persistent outbound connection.
|
||||||
|
if !p.inbound && p.persistent {
|
||||||
|
addr := p.conn.RemoteAddr().String()
|
||||||
|
s.ConnectPeerAsync(addr, true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBanPeerMsg deals with banning peers. It is invoked from the
|
||||||
|
// peerHandler goroutine.
|
||||||
|
func (s *server) handleBanPeerMsg(banned map[string]time.Time, p *peer) {
|
||||||
|
host, _, err := net.SplitHostPort(p.conn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[SRVR] %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
direction := directionString(p.inbound)
|
||||||
|
log.Infof("[SRVR] Banned peer %s (%s) for %v", host, direction,
|
||||||
|
cfg.BanDuration)
|
||||||
|
banned[host] = time.Now().Add(cfg.BanDuration)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBroadcastMsg deals with broadcasting messages to peers. It is invoked
|
||||||
|
// from the peerHandler goroutine.
|
||||||
|
func (s *server) handleBroadcastMsg(peers *list.List, bmsg *broadcastMsg) {
|
||||||
|
for e := peers.Front(); e != nil; e = e.Next() {
|
||||||
|
excluded := false
|
||||||
|
for _, p := range bmsg.excludePeers {
|
||||||
|
if e.Value == p {
|
||||||
|
excluded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !excluded {
|
||||||
|
p := e.Value.(*peer)
|
||||||
|
p.QueueMessage(bmsg.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// listenHandler is the main listener which accepts incoming connections for the
|
||||||
|
// server. It must be run as a goroutine.
|
||||||
|
func (s *server) listenHandler() {
|
||||||
|
log.Infof("[SRVR] Server listening on %s", s.listener.Addr())
|
||||||
|
for !s.shutdown {
|
||||||
|
conn, err := s.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
// Only log the error if we're not forcibly shutting down.
|
||||||
|
if !s.shutdown {
|
||||||
|
log.Errorf("[SRVR] %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.AddPeer(newPeer(s, conn, true, false))
|
||||||
|
}
|
||||||
|
s.wg.Done()
|
||||||
|
log.Tracef("[SRVR] Listener handler done for %s", s.listener.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// peerHandler is used to handle peer operations such as adding and removing
|
||||||
|
// peers to and from the server, banning peers, and broadcasting messages to
|
||||||
|
// peers. It must be run a a goroutine.
|
||||||
|
func (s *server) peerHandler() {
|
||||||
|
log.Tracef("[SRVR] Starting peer handler for %s", s.listener.Addr())
|
||||||
|
peers := list.New()
|
||||||
|
bannedPeers := make(map[string]time.Time)
|
||||||
|
|
||||||
|
// Live while we're not shutting down or there are still connected peers.
|
||||||
|
for !s.shutdown || peers.Len() != 0 {
|
||||||
|
select {
|
||||||
|
|
||||||
|
// New peers connected to the server.
|
||||||
|
case p := <-s.newPeers:
|
||||||
|
s.handleAddPeerMsg(peers, bannedPeers, p)
|
||||||
|
|
||||||
|
// Disconnected peers.
|
||||||
|
case p := <-s.donePeers:
|
||||||
|
s.handleDonePeerMsg(peers, p)
|
||||||
|
|
||||||
|
// Peer to ban.
|
||||||
|
case p := <-s.banPeers:
|
||||||
|
s.handleBanPeerMsg(bannedPeers, p)
|
||||||
|
|
||||||
|
// Message to broadcast to all connected peers except those
|
||||||
|
// which are excluded by the message.
|
||||||
|
case bmsg := <-s.broadcast:
|
||||||
|
s.handleBroadcastMsg(peers, &bmsg)
|
||||||
|
|
||||||
|
// Shutdown the peer handler.
|
||||||
|
case <-s.quit:
|
||||||
|
// Shutdown peers.
|
||||||
|
for e := peers.Front(); e != nil; e = e.Next() {
|
||||||
|
p := e.Value.(*peer)
|
||||||
|
p.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.wg.Done()
|
||||||
|
log.Tracef("[SRVR] Peer handler done on %s", s.listener.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPeer adds a new peer that has already been connected to the server.
|
||||||
|
func (s *server) AddPeer(p *peer) {
|
||||||
|
s.newPeers <- p
|
||||||
|
}
|
||||||
|
|
||||||
|
// BanPeer bans a peer that has already been connected to the server by ip.
|
||||||
|
func (s *server) BanPeer(p *peer) {
|
||||||
|
s.banPeers <- p
|
||||||
|
}
|
||||||
|
|
||||||
|
// BroadcastMessage sends msg to all peers currently connected to the server
|
||||||
|
// except those in the passed peers to exclude.
|
||||||
|
func (s *server) BroadcastMessage(msg btcwire.Message, exclPeers ...*peer) {
|
||||||
|
// XXX: Need to determine if this is an alert that has already been
|
||||||
|
// broadcast and refrain from broadcasting again.
|
||||||
|
bmsg := broadcastMsg{message: msg, excludePeers: exclPeers}
|
||||||
|
s.broadcast <- bmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectPeerAsync attempts to asynchronously connect to addr. If successful,
|
||||||
|
// a new peer is created and added to the server's peer list.
|
||||||
|
func (s *server) ConnectPeerAsync(addr string, persistent bool) {
|
||||||
|
// Don't try to connect to a peer if the server is shutting down.
|
||||||
|
if s.shutdown {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Attempt to connect to the peer. If the connection fails and
|
||||||
|
// this is a persistent connection, retry after the retry
|
||||||
|
// interval.
|
||||||
|
for !s.shutdown {
|
||||||
|
log.Debugf("[SRVR] Attempting to connect to %s", addr)
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("[SRVR] %v", err)
|
||||||
|
if !persistent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("[SRVR] Retrying connection to %s "+
|
||||||
|
"in %s", addr, connectionRetryInterval)
|
||||||
|
time.Sleep(connectionRetryInterval)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection was successful so log it and create a new
|
||||||
|
// peer.
|
||||||
|
log.Infof("[SRVR] Connected to %s", conn.RemoteAddr())
|
||||||
|
s.AddPeer(newPeer(s, conn, false, persistent))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins accepting connections from peers.
|
||||||
|
func (s *server) Start() {
|
||||||
|
// Already started?
|
||||||
|
if s.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("[SRVR] Starting server")
|
||||||
|
go s.listenHandler()
|
||||||
|
go s.peerHandler()
|
||||||
|
s.wg.Add(2)
|
||||||
|
s.addrManager.Start()
|
||||||
|
s.blockManager.Start()
|
||||||
|
if !cfg.DisableRpc {
|
||||||
|
s.rpcServer.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully shuts down the server by stopping and disconnecting all
|
||||||
|
// peers and the main listener.
|
||||||
|
func (s *server) Stop() error {
|
||||||
|
if s.shutdown {
|
||||||
|
log.Infof("[SRVR] Server is already in the process of shutting down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnf("[SRVR] Server shutting down")
|
||||||
|
s.shutdown = true
|
||||||
|
s.quit <- true
|
||||||
|
if !cfg.DisableRpc {
|
||||||
|
s.rpcServer.Stop()
|
||||||
|
}
|
||||||
|
s.addrManager.Stop()
|
||||||
|
s.blockManager.Stop()
|
||||||
|
err := s.listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForShutdown blocks until the main listener and peer handlers are stopped.
|
||||||
|
func (s *server) WaitForShutdown() {
|
||||||
|
s.wg.Wait()
|
||||||
|
log.Infof("[SRVR] Server shutdown complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScheduleShutdown schedules a server shutdown after the specified duration.
|
||||||
|
// It also dynamically adjusts how often to warn the server is going down based
|
||||||
|
// on remaining duration.
|
||||||
|
func (s *server) ScheduleShutdown(duration time.Duration) {
|
||||||
|
// Don't schedule shutdown more than once.
|
||||||
|
if s.shutdownSched {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warnf("[SRVR] Server shutdown in %v", duration)
|
||||||
|
go func() {
|
||||||
|
remaining := duration
|
||||||
|
tickDuration := dynamicTickDuration(remaining)
|
||||||
|
done := time.After(remaining)
|
||||||
|
ticker := time.NewTicker(tickDuration)
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
ticker.Stop()
|
||||||
|
s.Stop()
|
||||||
|
break out
|
||||||
|
case <-ticker.C:
|
||||||
|
remaining = remaining - tickDuration
|
||||||
|
if remaining < time.Second {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change tick duration dynamically based on remaining time.
|
||||||
|
newDuration := dynamicTickDuration(remaining)
|
||||||
|
if tickDuration != newDuration {
|
||||||
|
tickDuration = newDuration
|
||||||
|
ticker.Stop()
|
||||||
|
ticker = time.NewTicker(tickDuration)
|
||||||
|
}
|
||||||
|
log.Warnf("[SRVR] Server shutdown in %v", remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.shutdownSched = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// newServer returns a new btcd server configured to listen on addr for the
|
||||||
|
// bitcoin network type specified in btcnet. Use start to begin accepting
|
||||||
|
// connections from peers.
|
||||||
|
func newServer(addr string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, error) {
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := server{
|
||||||
|
nonce: nonce,
|
||||||
|
listener: listener,
|
||||||
|
btcnet: btcnet,
|
||||||
|
addrManager: addrmgr.New(),
|
||||||
|
newPeers: make(chan *peer, cfg.MaxPeers),
|
||||||
|
donePeers: make(chan *peer, cfg.MaxPeers),
|
||||||
|
banPeers: make(chan *peer, cfg.MaxPeers),
|
||||||
|
broadcast: make(chan broadcastMsg, cfg.MaxPeers),
|
||||||
|
quit: make(chan bool),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
s.blockManager = newBlockManager(&s)
|
||||||
|
|
||||||
|
log.Infof("[BMGR] Generating initial block node index. This may " +
|
||||||
|
"take a while...")
|
||||||
|
err = s.blockManager.blockChain.GenerateInitialIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Infof("[BMGR] Block index generation complete")
|
||||||
|
|
||||||
|
if !cfg.DisableRpc {
|
||||||
|
s.rpcServer, err = newRpcServer(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamicTickDuration is a convenience function used to dynamically choose a
|
||||||
|
// tick duration based on remaining time. It is primarily used during
|
||||||
|
// server shutdown to make shutdown warnings more frequent as the shutdown time
|
||||||
|
// approaches.
|
||||||
|
func dynamicTickDuration(remaining time.Duration) time.Duration {
|
||||||
|
switch {
|
||||||
|
case remaining <= time.Second*5:
|
||||||
|
return time.Second
|
||||||
|
case remaining <= time.Second*15:
|
||||||
|
return time.Second * 5
|
||||||
|
case remaining <= time.Minute:
|
||||||
|
return time.Second * 15
|
||||||
|
case remaining <= time.Minute*5:
|
||||||
|
return time.Minute
|
||||||
|
case remaining <= time.Minute*15:
|
||||||
|
return time.Minute * 5
|
||||||
|
case remaining <= time.Hour:
|
||||||
|
return time.Minute * 15
|
||||||
|
}
|
||||||
|
return time.Hour
|
||||||
|
}
|
36
btcd/signal.go
Normal file
36
btcd/signal.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
|
||||||
|
var interruptChannel chan os.Signal
|
||||||
|
|
||||||
|
// interruptCallbacks is a list of callbacks to invoke when a SIGINT (Ctrl+C) is
|
||||||
|
// received.
|
||||||
|
var interruptCallbacks []func()
|
||||||
|
|
||||||
|
// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is
|
||||||
|
// received.
|
||||||
|
func addInterruptHandler(handler func()) {
|
||||||
|
// Create the channel and start the main interrupt handler which invokes
|
||||||
|
// all other callbacks and exits if not already done.
|
||||||
|
if interruptChannel == nil {
|
||||||
|
interruptChannel = make(chan os.Signal, 1)
|
||||||
|
signal.Notify(interruptChannel, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-interruptChannel
|
||||||
|
for _, callback := range interruptCallbacks {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
interruptCallbacks = append(interruptCallbacks, handler)
|
||||||
|
}
|
Loading…
Reference in a new issue