diff --git a/Dockerfile b/Dockerfile index 4c3f8e90..18242330 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.10.3 -WORKDIR /go/src/github.com/HcashOrg/hcd +WORKDIR /go/src/github.com/james-ray/hcd COPY . . RUN go get -u github.com/golang/dep/cmd/dep diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 99b49cc2..14a3c57e 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,7 +1,7 @@ # Build image FROM golang:1.10.3 -WORKDIR /go/src/github.com/HcashOrg/hcd +WORKDIR /go/src/github.com/james-ray/hcd COPY . . RUN go get -u github.com/golang/dep/cmd/dep diff --git a/README.md b/README.md index 571b214a..947e4056 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ hcd maintains the entire past transactional ledger of Hc and allows relaying of quantum resistant transactions to other Hc nodes across the world. Note: To send or receive funds and join Proof-of-Stake mining, you will also need -[hcwallet](https://github.com/HcashOrg/hcwallet). +[hcwallet](https://github.com/james-ray/hcwallet). -HC is forked from [Decred](https://github.com/HcashOrg) and [btcd](https://github.com/btcsuite/btcd) which are full node implementations written in Go. Both projects are ongoing and under active development. Since hcd is synced and will merge with upstream commits from hcd and btcd, it will get the benefit of both hcd and btcd's ongoing upgrades to staking, voting, peer and connection handling, database optimization and other blockchain related technology improvements. Advances made by hcd can also be pulled back upstream to hcd and btcd including quantum resistant signature schemes and more. +HC is forked from [Decred](https://github.com/james-ray) and [btcd](https://github.com/btcsuite/btcd) which are full node implementations written in Go. Both projects are ongoing and under active development. Since hcd is synced and will merge with upstream commits from hcd and btcd, it will get the benefit of both hcd and btcd's ongoing upgrades to staking, voting, peer and connection handling, database optimization and other blockchain related technology improvements. Advances made by hcd can also be pulled back upstream to hcd and btcd including quantum resistant signature schemes and more. ## Development Process The master branch is regularly built and tested, but is not guaranteed to be completely stable. Tags are created regularly to indicate new official, stable release versions of hcd. @@ -39,8 +39,8 @@ For a first time installation, the project and dependency sources can be obtained manually with `git` ``` -git clone https://github.com/HcashOrg/hcd $GOPATH/src/github.com/HcashOrg/hcd -cd $GOPATH/src/github.com/HcashOrg/hcd +git clone https://github.com/james-ray/hcd $GOPATH/src/github.com/james-ray/hcd +cd $GOPATH/src/github.com/james-ray/hcd go install . ./cmd/... ``` @@ -48,7 +48,7 @@ To update an existing source tree, pull the latest changes and install the matching dependencies: ``` -cd $GOPATH/src/github.com/HcashOrg/hcd +cd $GOPATH/src/github.com/james-ray/hcd git pull go install . ./cmd/... ``` @@ -67,7 +67,7 @@ To use your node for mining add the miningaddr flag when running hcd: hcd -u YOURUNIQUERPCUSERNAME -P YOURUNIQUERPCPASSWORD --testnet --miningaddr=YOURTESTNETADDRESS ``` -To generate a testnet mining address you must install [hcwallet](https://github.com/HcashOrg/hcwallet) +To generate a testnet mining address you must install [hcwallet](https://github.com/james-ray/hcwallet) To begin CPU mining after hcd is already running you can run the following in your terminal: @@ -82,7 +82,7 @@ hcctl -u YOURUNIQUEUSERNAME -P YOURUNIQUEPASSWORD --testnet setgenerate true ``` $ rm -rf $HOME/.hcd $ rm -rf $HOME/.hcwallet -$ cd $HOME/go/src/github.com/HcashOrg/hcd +$ cd $HOME/go/src/github.com/james-ray/hcd $ git pull $ go install $(glide nv) ``` @@ -91,19 +91,19 @@ $ go install $(glide nv) ``` $ rm -rf $HOME/Library/Application\ Support/Hcwallet $ rm -rf $HOME/Library/Application\ Support/Hcd -$ cd $HOME/go/src/github.com/HcashOrg/hcd +$ cd $HOME/go/src/github.com/james-ray/hcd $ git pull $ go install $(glide nv) ``` ## Issue Tracker -The [integrated github issue tracker](https://github.com/HcashOrg/hcd/issues) +The [integrated github issue tracker](https://github.com/james-ray/hcd/issues) is used for this project. ## Documentation -The documentation is a work-in-progress. It is located in the [docs](https://github.com/HcashOrg/hcd/tree/master/docs) folder. +The documentation is a work-in-progress. It is located in the [docs](https://github.com/james-ray/hcd/tree/master/docs) folder. ## License diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index f620c114..9f49f6a5 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -1,1146 +1,1147 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr - -import ( - "container/list" - crand "crypto/rand" // for seeding - "encoding/base32" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "math/rand" - "net" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/wire" -) - -// AddrManager provides a concurrency safe address manager for caching potential -// peers on the hcd network. -type AddrManager struct { - mtx sync.Mutex - peersFile string - lookupFunc func(string) ([]net.IP, error) - rand *rand.Rand - key [32]byte - addrIndex map[string]*KnownAddress // address key to ka for all addrs. - addrNew [newBucketCount]map[string]*KnownAddress - addrTried [triedBucketCount]*list.List - started int32 - shutdown int32 - wg sync.WaitGroup - quit chan struct{} - nTried int - nNew int - lamtx sync.Mutex - localAddresses map[string]*localAddress -} - -type serializedKnownAddress struct { - Addr string - Src string - Attempts int - TimeStamp int64 - LastAttempt int64 - LastSuccess int64 - // no refcount or tried, that is available from context. -} - -type serializedAddrManager struct { - Version int - Key [32]byte - Addresses []*serializedKnownAddress - NewBuckets [newBucketCount][]string // string is NetAddressKey - TriedBuckets [triedBucketCount][]string -} - -type localAddress struct { - na *wire.NetAddress - score AddressPriority -} - -// AddressPriority type is used to describe the hierarchy of local address -// discovery methods. -type AddressPriority int - -const ( - // InterfacePrio signifies the address is on a local interface - InterfacePrio AddressPriority = iota - - // BoundPrio signifies the address has been explicitly bounded to. - BoundPrio - - // UpnpPrio signifies the address was obtained from UPnP. - UpnpPrio - - // HTTPPrio signifies the address was obtained from an external HTTP service. - HTTPPrio - - // ManualPrio signifies the address was provided by --externalip. - ManualPrio -) - -const ( - // needAddressThreshold is the number of addresses under which the - // address manager will claim to need more addresses. - needAddressThreshold = 1000 - - // dumpAddressInterval is the interval used to dump the address - // cache to disk for future use. - dumpAddressInterval = time.Minute * 10 - - // triedBucketSize is the maximum number of addresses in each - // tried address bucket. - triedBucketSize = 256 - - // triedBucketCount is the number of buckets we split tried - // addresses over. - triedBucketCount = 64 - - // newBucketSize is the maximum number of addresses in each new address - // bucket. - newBucketSize = 64 - - // newBucketCount is the number of buckets that we spread new addresses - // over. - newBucketCount = 1024 - - // triedBucketsPerGroup is the number of tried buckets over which an - // address group will be spread. - triedBucketsPerGroup = 8 - - // newBucketsPerGroup is the number of new buckets over which an - // source address group will be spread. - newBucketsPerGroup = 64 - - // newBucketsPerAddress is the number of buckets a frequently seen new - // address may end up in. - newBucketsPerAddress = 8 - - // numMissingDays is the number of days before which we assume an - // address has vanished if we have not seen it announced in that long. - numMissingDays = 30 - - // numRetries is the number of tried without a single success before - // we assume an address is bad. - numRetries = 3 - - // maxFailures is the maximum number of failures we will accept without - // a success before considering an address bad. - maxFailures = 10 - - // minBadDays is the number of days since the last success before we - // will consider evicting an address. - minBadDays = 7 - - // getAddrMax is the most addresses that we will send in response - // to a getAddr (in practise the most addresses we will return from a - // call to AddressCache()). - getAddrMax = 2500 - - // getAddrPercent is the percentage of total addresses known that we - // will share with a call to AddressCache. - getAddrPercent = 23 - - // serialisationVersion is the current version of the on-disk format. - serialisationVersion = 1 -) - -// 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 (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddress) { - // Filter out non-routable addresses. Note that non-routable - // also includes invalid and local addresses. - if !IsRoutable(netAddr) { - return - } - - addr := NetAddressKey(netAddr) - ka := a.find(netAddr) - if ka != nil { - // TODO(oga) only update addresses periodically. - // Update the last seen time and services. - // note that to prevent causing excess garbage on getaddr - // messages the netaddresses in addrmaanger are *immutable*, - // if we need to change them then we replace the pointer with a - // new copy so that we don't have to copy every na for getaddr. - if netAddr.Timestamp.After(ka.na.Timestamp) || - (ka.na.Services&netAddr.Services) != - netAddr.Services { - - naCopy := *ka.na - naCopy.Timestamp = netAddr.Timestamp - naCopy.AddService(netAddr.Services) - ka.mtx.Lock() - ka.na = &naCopy - ka.mtx.Unlock() - } - - // If already in tried, we have nothing to do here. - if ka.tried { - return - } - - // Already at our max? - if ka.refs == newBucketsPerAddress { - return - } - - // The more entries we have, the less likely we are to add more. - // likelihood is 2N. - factor := int32(2 * ka.refs) - if a.rand.Int31n(factor) != 0 { - return - } - } else { - // Make a copy of the net address to avoid races since it is - // updated elsewhere in the addrmanager code and would otherwise - // change the actual netaddress on the peer. - netAddrCopy := *netAddr - ka = &KnownAddress{na: &netAddrCopy, srcAddr: srcAddr} - a.addrIndex[addr] = ka - a.nNew++ - // XXX time penalty? - } - - bucket := a.getNewBucket(netAddr, srcAddr) - - // Already exists? - if _, ok := a.addrNew[bucket][addr]; ok { - return - } - - // Enforce max addresses. - if len(a.addrNew[bucket]) > newBucketSize { - log.Tracef("new bucket is full, expiring old") - a.expireNew(bucket) - } - - // Add to new bucket. - ka.refs++ - a.addrNew[bucket][addr] = ka - - log.Tracef("Added new address %s for a total of %d addresses", addr, - a.nTried+a.nNew) -} - -// expireNew makes space in the new buckets by expiring the really bad entries. -// If no bad entries are available we look at a few and remove the oldest. -func (a *AddrManager) expireNew(bucket int) { - // First see if there are any entries that are so bad we can just throw - // them away. otherwise we throw away the oldest entry in the cache. - // Bitcoind here chooses four random and just throws the oldest of - // those away, but we keep track of oldest in the initial traversal and - // use that information instead. - var oldest *KnownAddress - for k, v := range a.addrNew[bucket] { - if v.isBad() { - log.Tracef("expiring bad address %v", k) - delete(a.addrNew[bucket], k) - v.refs-- - if v.refs == 0 { - a.nNew-- - delete(a.addrIndex, k) - } - continue - } - if oldest == nil { - oldest = v - } else if !v.na.Timestamp.After(oldest.na.Timestamp) { - oldest = v - } - } - - if oldest != nil { - key := NetAddressKey(oldest.na) - log.Tracef("expiring oldest address %v", key) - - delete(a.addrNew[bucket], key) - oldest.refs-- - if oldest.refs == 0 { - a.nNew-- - delete(a.addrIndex, key) - } - } -} - -// pickTried selects an address from the tried bucket to be evicted. -// We just choose the eldest. Bitcoind selects 4 random entries and throws away -// the older of them. -func (a *AddrManager) pickTried(bucket int) *list.Element { - var oldest *KnownAddress - var oldestElem *list.Element - for e := a.addrTried[bucket].Front(); e != nil; e = e.Next() { - ka := e.Value.(*KnownAddress) - if oldest == nil || oldest.na.Timestamp.After(ka.na.Timestamp) { - oldestElem = e - oldest = ka - } - - } - return oldestElem -} - -func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddress) int { - // bitcoind: - // doublesha256(key + sourcegroup + int64(doublesha256(key + group - // + sourcegroup))%bucket_per_source_group) % num_new_buckets - - data1 := []byte{} - data1 = append(data1, a.key[:]...) - data1 = append(data1, []byte(GroupKey(netAddr))...) - data1 = append(data1, []byte(GroupKey(srcAddr))...) - hash1 := chainhash.HashB(data1) - hash64 := binary.LittleEndian.Uint64(hash1) - hash64 %= newBucketsPerGroup - var hashbuf [8]byte - binary.LittleEndian.PutUint64(hashbuf[:], hash64) - data2 := []byte{} - data2 = append(data2, a.key[:]...) - data2 = append(data2, GroupKey(srcAddr)...) - data2 = append(data2, hashbuf[:]...) - - hash2 := chainhash.HashB(data2) - return int(binary.LittleEndian.Uint64(hash2) % newBucketCount) -} - -func (a *AddrManager) getTriedBucket(netAddr *wire.NetAddress) int { - // bitcoind hashes this as: - // doublesha256(key + group + truncate_to_64bits(doublesha256(key)) - // % buckets_per_group) % num_buckets - data1 := []byte{} - data1 = append(data1, a.key[:]...) - data1 = append(data1, []byte(NetAddressKey(netAddr))...) - hash1 := chainhash.HashB(data1) - hash64 := binary.LittleEndian.Uint64(hash1) - hash64 %= triedBucketsPerGroup - var hashbuf [8]byte - binary.LittleEndian.PutUint64(hashbuf[:], hash64) - data2 := []byte{} - data2 = append(data2, a.key[:]...) - data2 = append(data2, GroupKey(netAddr)...) - data2 = append(data2, hashbuf[:]...) - - hash2 := chainhash.HashB(data2) - return int(binary.LittleEndian.Uint64(hash2) % triedBucketCount) -} - -// addressHandler is the main handler for the address manager. It must be run -// as a goroutine. -func (a *AddrManager) addressHandler() { - dumpAddressTicker := time.NewTicker(dumpAddressInterval) - defer dumpAddressTicker.Stop() -out: - for { - select { - case <-dumpAddressTicker.C: - a.savePeers() - - case <-a.quit: - break out - } - } - a.savePeers() - a.wg.Done() - log.Trace("Address handler done") -} - -// savePeers saves all the known addresses to a file so they can be read back -// in at next run. -func (a *AddrManager) savePeers() { - a.mtx.Lock() - defer a.mtx.Unlock() - - // First we make a serialisable datastructure so we can encode it to - // json. - sam := new(serializedAddrManager) - sam.Version = serialisationVersion - copy(sam.Key[:], a.key[:]) - - sam.Addresses = make([]*serializedKnownAddress, len(a.addrIndex)) - i := 0 - for k, v := range a.addrIndex { - ska := new(serializedKnownAddress) - ska.Addr = k - ska.TimeStamp = v.na.Timestamp.Unix() - ska.Src = NetAddressKey(v.srcAddr) - ska.Attempts = v.attempts - ska.LastAttempt = v.lastattempt.Unix() - ska.LastSuccess = v.lastsuccess.Unix() - // Tried and refs are implicit in the rest of the structure - // and will be worked out from context on unserialisation. - sam.Addresses[i] = ska - i++ - } - for i := range a.addrNew { - sam.NewBuckets[i] = make([]string, len(a.addrNew[i])) - j := 0 - for k := range a.addrNew[i] { - sam.NewBuckets[i][j] = k - j++ - } - } - for i := range a.addrTried { - sam.TriedBuckets[i] = make([]string, a.addrTried[i].Len()) - j := 0 - for e := a.addrTried[i].Front(); e != nil; e = e.Next() { - ka := e.Value.(*KnownAddress) - sam.TriedBuckets[i][j] = NetAddressKey(ka.na) - j++ - } - } - - w, err := os.Create(a.peersFile) - if err != nil { - log.Errorf("Error opening file %s: %v", a.peersFile, err) - return - } - enc := json.NewEncoder(w) - defer w.Close() - if err := enc.Encode(&sam); err != nil { - log.Errorf("Failed to encode file %s: %v", a.peersFile, err) - return - } -} - -// loadPeers loads the known address from the saved file. If empty, missing, or -// malformed file, just don't load anything and start fresh -func (a *AddrManager) loadPeers() { - a.mtx.Lock() - defer a.mtx.Unlock() - - err := a.deserializePeers(a.peersFile) - if err != nil { - log.Errorf("Failed to parse file %s: %v", a.peersFile, err) - // if it is invalid we nuke the old one unconditionally. - err = os.Remove(a.peersFile) - if err != nil { - log.Warnf("Failed to remove corrupt peers file %s: %v", - a.peersFile, err) - } - a.reset() - return - } - log.Infof("Loaded %d addresses from file '%s'", a.numAddresses(), a.peersFile) -} - -func (a *AddrManager) deserializePeers(filePath string) error { - - _, err := os.Stat(filePath) - if os.IsNotExist(err) { - return nil - } - r, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("%s error opening file: %v", filePath, err) - } - defer r.Close() - - var sam serializedAddrManager - dec := json.NewDecoder(r) - err = dec.Decode(&sam) - if err != nil { - return fmt.Errorf("error reading %s: %v", filePath, err) - } - - if sam.Version != serialisationVersion { - return fmt.Errorf("unknown version %v in serialized "+ - "addrmanager", sam.Version) - } - copy(a.key[:], sam.Key[:]) - - for _, v := range sam.Addresses { - ka := new(KnownAddress) - ka.na, err = a.DeserializeNetAddress(v.Addr) - if err != nil { - return fmt.Errorf("failed to deserialize netaddress "+ - "%s: %v", v.Addr, err) - } - ka.srcAddr, err = a.DeserializeNetAddress(v.Src) - if err != nil { - return fmt.Errorf("failed to deserialize netaddress "+ - "%s: %v", v.Src, err) - } - ka.attempts = v.Attempts - ka.lastattempt = time.Unix(v.LastAttempt, 0) - ka.lastsuccess = time.Unix(v.LastSuccess, 0) - a.addrIndex[NetAddressKey(ka.na)] = ka - } - - for i := range sam.NewBuckets { - for _, val := range sam.NewBuckets[i] { - ka, ok := a.addrIndex[val] - if !ok { - return fmt.Errorf("new buckets contains %s but "+ - "none in address list", val) - } - - if ka.refs == 0 { - a.nNew++ - } - ka.refs++ - a.addrNew[i][val] = ka - } - } - for i := range sam.TriedBuckets { - for _, val := range sam.TriedBuckets[i] { - ka, ok := a.addrIndex[val] - if !ok { - return fmt.Errorf("tried buckets contains %s but "+ - "none in address list", val) - } - - ka.tried = true - a.nTried++ - a.addrTried[i].PushBack(ka) - } - } - - // Sanity checking. - for k, v := range a.addrIndex { - if v.refs == 0 && !v.tried { - return fmt.Errorf("address %s after serialisation "+ - "with no references", k) - } - - if v.refs > 0 && v.tried { - return fmt.Errorf("address %s after serialisation "+ - "which is both new and tried!", k) - } - } - - return nil -} - -// DeserializeNetAddress converts a given address string to a *wire.NetAddress -func (a *AddrManager) DeserializeNetAddress(addr string) (*wire.NetAddress, error) { - host, portStr, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, err - } - - return a.HostToNetAddress(host, uint16(port), wire.SFNodeNetwork) -} - -// 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 atomic.AddInt32(&a.started, 1) != 1 { - return - } - - log.Trace("Starting address manager") - - // Load peers we already know about from file. - a.loadPeers() - - // Start the address ticker to save addresses periodically. - a.wg.Add(1) - go a.addressHandler() -} - -// Stop gracefully shuts down the address manager by stopping the main handler. -func (a *AddrManager) Stop() error { - if atomic.AddInt32(&a.shutdown, 1) != 1 { - log.Warnf("Address manager is already in the process of " + - "shutting down") - return nil - } - - log.Infof("Address manager shutting down") - close(a.quit) - 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 []*wire.NetAddress, srcAddr *wire.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - - for _, na := range addrs { - a.updateAddress(na, srcAddr) - } -} - -// 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, srcAddr *wire.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - - a.updateAddress(addr, srcAddr) -} - -// AddAddressByIP adds an address where we are given an ip:port and not a -// wire.NetAddress. -func (a *AddrManager) AddAddressByIP(addrIP string) error { - // Split IP and port - addr, portStr, err := net.SplitHostPort(addrIP) - if err != nil { - return err - } - // Put it in wire.Netaddress - ip := net.ParseIP(addr) - if ip == nil { - return fmt.Errorf("invalid ip address %s", addr) - } - port, err := strconv.ParseUint(portStr, 10, 0) - if err != nil { - return fmt.Errorf("invalid port %s: %v", portStr, err) - } - na := wire.NewNetAddressIPPort(ip, uint16(port), 0) - a.AddAddress(na, na) // XXX use correct src address - return nil -} - -// NumAddresses returns the number of addresses known to the address manager. -func (a *AddrManager) numAddresses() int { - return a.nTried + a.nNew -} - -// NumAddresses returns the number of addresses known to the address manager. -func (a *AddrManager) NumAddresses() int { - a.mtx.Lock() - defer a.mtx.Unlock() - - return a.numAddresses() -} - -// NeedMoreAddresses returns whether or not the address manager needs more -// addresses. -func (a *AddrManager) NeedMoreAddresses() bool { - a.mtx.Lock() - defer a.mtx.Unlock() - - return a.numAddresses() < needAddressThreshold -} - -// AddressCache returns the current address cache. It must be treated as -// read-only (but since it is a copy now, this is not as dangerous). -func (a *AddrManager) AddressCache() []*wire.NetAddress { - //a.mtx.Lock() - //defer a.mtx.Unlock() - // - //addrIndexLen := len(a.addrIndex) - //if addrIndexLen == 0 { - // return nil - //} - // - //allAddr := make([]*wire.NetAddress, 0, addrIndexLen) - //// Iteration order is undefined here, but we randomise it anyway. - //for _, v := range a.addrIndex { - // allAddr = append(allAddr, v.na) - //} - allAddr := a.getAddresses() - - //numAddresses := addrIndexLen * getAddrPercent / 100 - numAddresses := len(allAddr) * getAddrPercent / 100 - if numAddresses > getAddrMax { - numAddresses = getAddrMax - } - - // Fisher-Yates shuffle the array. We only need to do the first - // `numAddresses' since we are throwing the rest. - for i := 0; i < numAddresses; i++ { - // pick a number between current index and the end - //j := rand.Intn(addrIndexLen-i) + i - j := rand.Intn(len(allAddr)-i) + i - allAddr[i], allAddr[j] = allAddr[j], allAddr[i] - } - - // slice off the limit we are willing to share. - return allAddr[0:numAddresses] -} -// getAddresses returns all of the addresses currently found within the -// manager's address cache. -func (a *AddrManager) getAddresses() []*wire.NetAddress { - a.mtx.Lock() - defer a.mtx.Unlock() - - addrIndexLen := len(a.addrIndex) - if addrIndexLen == 0 { - return nil - } - - addrs := make([]*wire.NetAddress, 0, addrIndexLen) - for _, v := range a.addrIndex { - addrs = append(addrs, v.na) - } - - return addrs -} -// reset resets the address manager by reinitialising the random source -// and allocating fresh empty bucket storage. -func (a *AddrManager) reset() { - - a.addrIndex = make(map[string]*KnownAddress) - - // fill key with bytes from a good random source. - io.ReadFull(crand.Reader, a.key[:]) - for i := range a.addrNew { - a.addrNew[i] = make(map[string]*KnownAddress) - } - for i := range a.addrTried { - a.addrTried[i] = list.New() - } -} - -// HostToNetAddress returns a netaddress given a host address. If the address is -// a tor .onion address this will be taken care of. else if the host is not an -// IP address it will be resolved (via tor if required). -func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*wire.NetAddress, error) { - // tor address is 16 char base32 + ".onion" - var ip net.IP - if len(host) == 22 && host[16:] == ".onion" { - // go base32 encoding uses capitals (as does the rfc - // but tor and bitcoind tend to user lowercase, so we switch - // case here. - data, err := base32.StdEncoding.DecodeString( - strings.ToUpper(host[:16])) - if err != nil { - return nil, err - } - prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} - ip = net.IP(append(prefix, data...)) - } else if ip = net.ParseIP(host); ip == nil { - ips, err := a.lookupFunc(host) - if err != nil { - return nil, err - } - if len(ips) == 0 { - return nil, fmt.Errorf("no addresses found for %s", host) - } - ip = ips[0] - } - - return wire.NewNetAddressIPPort(ip, port, services), nil -} - -// ipString returns a string for the ip from the provided NetAddress. If the -// ip is in the range used for tor addresses then it will be transformed into -// the relevant .onion address. -func ipString(na *wire.NetAddress) string { - if IsOnionCatTor(na) { - // We know now that na.IP is long enogh. - base32 := base32.StdEncoding.EncodeToString(na.IP[6:]) - return strings.ToLower(base32) + ".onion" - } - - return na.IP.String() -} - -// NetAddressKey returns a string key in the form of ip:port for IPv4 addresses -// or [ip]:port for IPv6 addresses. -func NetAddressKey(na *wire.NetAddress) string { - port := strconv.FormatUint(uint64(na.Port), 10) - - return net.JoinHostPort(ipString(na), port) -} - -// GetAddress returns a single address that should be routable. It picks a -// random one from the possible addresses with preference given to ones that -// have not been used recently and should not pick 'close' addresses -// consecutively. -func (a *AddrManager) GetAddress() *KnownAddress { - // Protect concurrent access. - a.mtx.Lock() - defer a.mtx.Unlock() - - if a.numAddresses() == 0 { - return nil - } - - // Use a 50% chance for choosing between tried and new table entries. - if a.nTried > 0 && (a.nNew == 0 || a.rand.Intn(2) == 0) { - // Tried entry. - large := 1 << 30 - factor := 1.0 - for { - // pick a random bucket. - bucket := a.rand.Intn(len(a.addrTried)) - if a.addrTried[bucket].Len() == 0 { - continue - } - - // Pick a random entry in the list - e := a.addrTried[bucket].Front() - for i := - a.rand.Int63n(int64(a.addrTried[bucket].Len())); i > 0; i-- { - e = e.Next() - } - ka := e.Value.(*KnownAddress) - randval := a.rand.Intn(large) - if float64(randval) < (factor * ka.chance() * float64(large)) { - log.Tracef("Selected %v from tried bucket", - NetAddressKey(ka.na)) - return ka - } - factor *= 1.2 - } - } else { - // new node. - // XXX use a closure/function to avoid repeating this. - large := 1 << 30 - factor := 1.0 - for { - // Pick a random bucket. - bucket := a.rand.Intn(len(a.addrNew)) - if len(a.addrNew[bucket]) == 0 { - continue - } - // Then, a random entry in it. - var ka *KnownAddress - nth := a.rand.Intn(len(a.addrNew[bucket])) - for _, value := range a.addrNew[bucket] { - if nth == 0 { - ka = value - } - nth-- - } - randval := a.rand.Intn(large) - if float64(randval) < (factor * ka.chance() * float64(large)) { - log.Tracef("Selected %v from new bucket", - NetAddressKey(ka.na)) - return ka - } - factor *= 1.2 - } - } -} - -func (a *AddrManager) find(addr *wire.NetAddress) *KnownAddress { - return a.addrIndex[NetAddressKey(addr)] -} - -// Attempt increases the given address' attempt counter and updates -// the last attempt time. -func (a *AddrManager) Attempt(addr *wire.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - - // find address. - // Surely address will be in tried by now? - ka := a.find(addr) - if ka == nil { - return - } - // set last tried time to now - ka.mtx.Lock() - ka.attempts++ - ka.lastattempt = time.Now() - ka.mtx.Unlock() -} - -// Connected Marks the given address as currently connected and working at the -// current time. The address must already be known to AddrManager else it will -// be ignored. -func (a *AddrManager) Connected(addr *wire.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - - ka := a.find(addr) - if ka == nil { - return - } - - // Update the time as long as it has been 20 minutes since last we did - // so. - now := time.Now() - if now.After(ka.na.Timestamp.Add(time.Minute * 20)) { - // ka.na is immutable, so replace it. - ka.mtx.Lock() - naCopy := *ka.na - naCopy.Timestamp = time.Now() - ka.na = &naCopy - ka.mtx.Unlock() - } -} - -// Good marks the given address as good. To be called after a successful -// connection and version exchange. If the address is unknown to the address -// manager it will be ignored. -func (a *AddrManager) Good(addr *wire.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - - ka := a.find(addr) - if ka == nil { - return - } - - // ka.Timestamp is not updated here to avoid leaking information - // about currently connected peers. - now := time.Now() - ka.lastsuccess = now - ka.lastattempt = now - ka.attempts = 0 - - // move to tried set, optionally evicting other addresses if neeed. - if ka.tried { - return - } - - // ok, need to move it to tried. - - // remove from all new buckets. - // record one of the buckets in question and call it the `first' - addrKey := NetAddressKey(addr) - oldBucket := -1 - for i := range a.addrNew { - // we check for existence so we can record the first one - if _, ok := a.addrNew[i][addrKey]; ok { - delete(a.addrNew[i], addrKey) - ka.refs-- - if oldBucket == -1 { - oldBucket = i - } - } - } - a.nNew-- - - if oldBucket == -1 { - // What? wasn't in a bucket after all.... Panic? - return - } - - bucket := a.getTriedBucket(ka.na) - - // Room in this tried bucket? - if a.addrTried[bucket].Len() < triedBucketSize { - ka.tried = true - a.addrTried[bucket].PushBack(ka) - a.nTried++ - return - } - - // No room, we have to evict something else. - entry := a.pickTried(bucket) - rmka := entry.Value.(*KnownAddress) - - // First bucket it would have been put in. - newBucket := a.getNewBucket(rmka.na, rmka.srcAddr) - - // If no room in the original bucket, we put it in a bucket we just - // freed up a space in. - if len(a.addrNew[newBucket]) >= newBucketSize { - newBucket = oldBucket - } - - // replace with ka in list. - ka.tried = true - entry.Value = ka - - rmka.tried = false - rmka.refs++ - - // We don't touch a.nTried here since the number of tried stays the same - // but we decemented new above, raise it again since we're putting - // something back. - a.nNew++ - - rmkey := NetAddressKey(rmka.na) - log.Tracef("Replacing %s with %s in tried", rmkey, addrKey) - - // We made sure there is space here just above. - a.addrNew[newBucket][rmkey] = rmka -} - -// AddLocalAddress adds na to the list of known local addresses to advertise -// with the given priority. -func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPriority) error { - if !IsRoutable(na) { - return fmt.Errorf("address %s is not routable", na.IP) - } - - a.lamtx.Lock() - defer a.lamtx.Unlock() - - key := NetAddressKey(na) - la, ok := a.localAddresses[key] - if !ok || la.score < priority { - if ok { - la.score = priority + 1 - } else { - a.localAddresses[key] = &localAddress{ - na: na, - score: priority, - } - } - } - return nil -} - -// getReachabilityFrom returns the relative reachability of the provided local -// address to the provided remote address. -func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { - const ( - Unreachable = 0 - Default = iota - Teredo - Ipv6Weak - Ipv4 - Ipv6Strong - Private - ) - - if !IsRoutable(remoteAddr) { - return Unreachable - } - - if IsOnionCatTor(remoteAddr) { - if IsOnionCatTor(localAddr) { - return Private - } - - if IsRoutable(localAddr) && IsIPv4(localAddr) { - return Ipv4 - } - - return Default - } - - if IsRFC4380(remoteAddr) { - if !IsRoutable(localAddr) { - return Default - } - - if IsRFC4380(localAddr) { - return Teredo - } - - if IsIPv4(localAddr) { - return Ipv4 - } - - return Ipv6Weak - } - - if IsIPv4(remoteAddr) { - if IsRoutable(localAddr) && IsIPv4(localAddr) { - return Ipv4 - } - return Unreachable - } - - /* ipv6 */ - var tunnelled bool - // Is our v6 is tunnelled? - if IsRFC3964(localAddr) || IsRFC6052(localAddr) || IsRFC6145(localAddr) { - tunnelled = true - } - - if !IsRoutable(localAddr) { - return Default - } - - if IsRFC4380(localAddr) { - return Teredo - } - - if IsIPv4(localAddr) { - return Ipv4 - } - - if tunnelled { - // only prioritise ipv6 if we aren't tunnelling it. - return Ipv6Weak - } - - return Ipv6Strong -} - -// GetBestLocalAddress returns the most appropriate local address to use -// for the given remote address. -func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.NetAddress { - a.lamtx.Lock() - defer a.lamtx.Unlock() - - bestreach := 0 - var bestscore AddressPriority - var bestAddress *wire.NetAddress - for _, la := range a.localAddresses { - reach := getReachabilityFrom(la.na, remoteAddr) - if reach > bestreach || - (reach == bestreach && la.score > bestscore) { - bestreach = reach - bestscore = la.score - bestAddress = la.na - } - } - if bestAddress != nil { - log.Debugf("Suggesting address %s:%d for %s:%d", bestAddress.IP, - bestAddress.Port, remoteAddr.IP, remoteAddr.Port) - } else { - log.Debugf("No worthy address for %s:%d", remoteAddr.IP, - remoteAddr.Port) - - // Send something unroutable if nothing suitable. - var ip net.IP - if !IsIPv4(remoteAddr) && !IsOnionCatTor(remoteAddr) { - ip = net.IPv6zero - } else { - ip = net.IPv4zero - } - bestAddress = wire.NewNetAddressIPPort(ip, 0, wire.SFNodeNetwork) - } - - return bestAddress -} - -// New returns a new hcd address manager. -// Use Start to begin processing asynchronous address updates. -func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager { - am := AddrManager{ - peersFile: filepath.Join(dataDir, "peers.json"), - lookupFunc: lookupFunc, - rand: rand.New(rand.NewSource(time.Now().UnixNano())), - quit: make(chan struct{}), - localAddresses: make(map[string]*localAddress), - } - am.reset() - return &am -} - - -// SetServices sets the services for the giiven address to the provided value. -func (a *AddrManager) SetServices(addr *wire.NetAddress, services wire.ServiceFlag) { - a.mtx.Lock() - defer a.mtx.Unlock() - - ka := a.find(addr) - if ka == nil { - return - } - - // Update the services if needed. - if ka.na.Services != services { - // ka.na is immutable, so replace it. - ka.mtx.Lock() - naCopy := *ka.na - naCopy.Services = services - ka.na = &naCopy - ka.mtx.Unlock() - } -} +// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "container/list" + crand "crypto/rand" // for seeding + "encoding/base32" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "math/rand" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/wire" +) + +// AddrManager provides a concurrency safe address manager for caching potential +// peers on the hcd network. +type AddrManager struct { + mtx sync.Mutex + peersFile string + lookupFunc func(string) ([]net.IP, error) + rand *rand.Rand + key [32]byte + addrIndex map[string]*KnownAddress // address key to ka for all addrs. + addrNew [newBucketCount]map[string]*KnownAddress + addrTried [triedBucketCount]*list.List + started int32 + shutdown int32 + wg sync.WaitGroup + quit chan struct{} + nTried int + nNew int + lamtx sync.Mutex + localAddresses map[string]*localAddress +} + +type serializedKnownAddress struct { + Addr string + Src string + Attempts int + TimeStamp int64 + LastAttempt int64 + LastSuccess int64 + // no refcount or tried, that is available from context. +} + +type serializedAddrManager struct { + Version int + Key [32]byte + Addresses []*serializedKnownAddress + NewBuckets [newBucketCount][]string // string is NetAddressKey + TriedBuckets [triedBucketCount][]string +} + +type localAddress struct { + na *wire.NetAddress + score AddressPriority +} + +// AddressPriority type is used to describe the hierarchy of local address +// discovery methods. +type AddressPriority int + +const ( + // InterfacePrio signifies the address is on a local interface + InterfacePrio AddressPriority = iota + + // BoundPrio signifies the address has been explicitly bounded to. + BoundPrio + + // UpnpPrio signifies the address was obtained from UPnP. + UpnpPrio + + // HTTPPrio signifies the address was obtained from an external HTTP service. + HTTPPrio + + // ManualPrio signifies the address was provided by --externalip. + ManualPrio +) + +const ( + // needAddressThreshold is the number of addresses under which the + // address manager will claim to need more addresses. + needAddressThreshold = 1000 + + // dumpAddressInterval is the interval used to dump the address + // cache to disk for future use. + dumpAddressInterval = time.Minute * 10 + + // triedBucketSize is the maximum number of addresses in each + // tried address bucket. + triedBucketSize = 256 + + // triedBucketCount is the number of buckets we split tried + // addresses over. + triedBucketCount = 64 + + // newBucketSize is the maximum number of addresses in each new address + // bucket. + newBucketSize = 64 + + // newBucketCount is the number of buckets that we spread new addresses + // over. + newBucketCount = 1024 + + // triedBucketsPerGroup is the number of tried buckets over which an + // address group will be spread. + triedBucketsPerGroup = 8 + + // newBucketsPerGroup is the number of new buckets over which an + // source address group will be spread. + newBucketsPerGroup = 64 + + // newBucketsPerAddress is the number of buckets a frequently seen new + // address may end up in. + newBucketsPerAddress = 8 + + // numMissingDays is the number of days before which we assume an + // address has vanished if we have not seen it announced in that long. + numMissingDays = 30 + + // numRetries is the number of tried without a single success before + // we assume an address is bad. + numRetries = 3 + + // maxFailures is the maximum number of failures we will accept without + // a success before considering an address bad. + maxFailures = 10 + + // minBadDays is the number of days since the last success before we + // will consider evicting an address. + minBadDays = 7 + + // getAddrMax is the most addresses that we will send in response + // to a getAddr (in practise the most addresses we will return from a + // call to AddressCache()). + getAddrMax = 2500 + + // getAddrPercent is the percentage of total addresses known that we + // will share with a call to AddressCache. + getAddrPercent = 23 + + // serialisationVersion is the current version of the on-disk format. + serialisationVersion = 1 +) + +// 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 (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddress) { + // Filter out non-routable addresses. Note that non-routable + // also includes invalid and local addresses. + if !IsRoutable(netAddr) { + return + } + + addr := NetAddressKey(netAddr) + ka := a.find(netAddr) + if ka != nil { + // TODO(oga) only update addresses periodically. + // Update the last seen time and services. + // note that to prevent causing excess garbage on getaddr + // messages the netaddresses in addrmaanger are *immutable*, + // if we need to change them then we replace the pointer with a + // new copy so that we don't have to copy every na for getaddr. + if netAddr.Timestamp.After(ka.na.Timestamp) || + (ka.na.Services&netAddr.Services) != + netAddr.Services { + + naCopy := *ka.na + naCopy.Timestamp = netAddr.Timestamp + naCopy.AddService(netAddr.Services) + ka.mtx.Lock() + ka.na = &naCopy + ka.mtx.Unlock() + } + + // If already in tried, we have nothing to do here. + if ka.tried { + return + } + + // Already at our max? + if ka.refs == newBucketsPerAddress { + return + } + + // The more entries we have, the less likely we are to add more. + // likelihood is 2N. + factor := int32(2 * ka.refs) + if a.rand.Int31n(factor) != 0 { + return + } + } else { + // Make a copy of the net address to avoid races since it is + // updated elsewhere in the addrmanager code and would otherwise + // change the actual netaddress on the peer. + netAddrCopy := *netAddr + ka = &KnownAddress{na: &netAddrCopy, srcAddr: srcAddr} + a.addrIndex[addr] = ka + a.nNew++ + // XXX time penalty? + } + + bucket := a.getNewBucket(netAddr, srcAddr) + + // Already exists? + if _, ok := a.addrNew[bucket][addr]; ok { + return + } + + // Enforce max addresses. + if len(a.addrNew[bucket]) > newBucketSize { + log.Tracef("new bucket is full, expiring old") + a.expireNew(bucket) + } + + // Add to new bucket. + ka.refs++ + a.addrNew[bucket][addr] = ka + + log.Tracef("Added new address %s for a total of %d addresses", addr, + a.nTried+a.nNew) +} + +// expireNew makes space in the new buckets by expiring the really bad entries. +// If no bad entries are available we look at a few and remove the oldest. +func (a *AddrManager) expireNew(bucket int) { + // First see if there are any entries that are so bad we can just throw + // them away. otherwise we throw away the oldest entry in the cache. + // Bitcoind here chooses four random and just throws the oldest of + // those away, but we keep track of oldest in the initial traversal and + // use that information instead. + var oldest *KnownAddress + for k, v := range a.addrNew[bucket] { + if v.isBad() { + log.Tracef("expiring bad address %v", k) + delete(a.addrNew[bucket], k) + v.refs-- + if v.refs == 0 { + a.nNew-- + delete(a.addrIndex, k) + } + continue + } + if oldest == nil { + oldest = v + } else if !v.na.Timestamp.After(oldest.na.Timestamp) { + oldest = v + } + } + + if oldest != nil { + key := NetAddressKey(oldest.na) + log.Tracef("expiring oldest address %v", key) + + delete(a.addrNew[bucket], key) + oldest.refs-- + if oldest.refs == 0 { + a.nNew-- + delete(a.addrIndex, key) + } + } +} + +// pickTried selects an address from the tried bucket to be evicted. +// We just choose the eldest. Bitcoind selects 4 random entries and throws away +// the older of them. +func (a *AddrManager) pickTried(bucket int) *list.Element { + var oldest *KnownAddress + var oldestElem *list.Element + for e := a.addrTried[bucket].Front(); e != nil; e = e.Next() { + ka := e.Value.(*KnownAddress) + if oldest == nil || oldest.na.Timestamp.After(ka.na.Timestamp) { + oldestElem = e + oldest = ka + } + + } + return oldestElem +} + +func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddress) int { + // bitcoind: + // doublesha256(key + sourcegroup + int64(doublesha256(key + group + // + sourcegroup))%bucket_per_source_group) % num_new_buckets + + data1 := []byte{} + data1 = append(data1, a.key[:]...) + data1 = append(data1, []byte(GroupKey(netAddr))...) + data1 = append(data1, []byte(GroupKey(srcAddr))...) + hash1 := chainhash.HashB(data1) + hash64 := binary.LittleEndian.Uint64(hash1) + hash64 %= newBucketsPerGroup + var hashbuf [8]byte + binary.LittleEndian.PutUint64(hashbuf[:], hash64) + data2 := []byte{} + data2 = append(data2, a.key[:]...) + data2 = append(data2, GroupKey(srcAddr)...) + data2 = append(data2, hashbuf[:]...) + + hash2 := chainhash.HashB(data2) + return int(binary.LittleEndian.Uint64(hash2) % newBucketCount) +} + +func (a *AddrManager) getTriedBucket(netAddr *wire.NetAddress) int { + // bitcoind hashes this as: + // doublesha256(key + group + truncate_to_64bits(doublesha256(key)) + // % buckets_per_group) % num_buckets + data1 := []byte{} + data1 = append(data1, a.key[:]...) + data1 = append(data1, []byte(NetAddressKey(netAddr))...) + hash1 := chainhash.HashB(data1) + hash64 := binary.LittleEndian.Uint64(hash1) + hash64 %= triedBucketsPerGroup + var hashbuf [8]byte + binary.LittleEndian.PutUint64(hashbuf[:], hash64) + data2 := []byte{} + data2 = append(data2, a.key[:]...) + data2 = append(data2, GroupKey(netAddr)...) + data2 = append(data2, hashbuf[:]...) + + hash2 := chainhash.HashB(data2) + return int(binary.LittleEndian.Uint64(hash2) % triedBucketCount) +} + +// addressHandler is the main handler for the address manager. It must be run +// as a goroutine. +func (a *AddrManager) addressHandler() { + dumpAddressTicker := time.NewTicker(dumpAddressInterval) + defer dumpAddressTicker.Stop() +out: + for { + select { + case <-dumpAddressTicker.C: + a.savePeers() + + case <-a.quit: + break out + } + } + a.savePeers() + a.wg.Done() + log.Trace("Address handler done") +} + +// savePeers saves all the known addresses to a file so they can be read back +// in at next run. +func (a *AddrManager) savePeers() { + a.mtx.Lock() + defer a.mtx.Unlock() + + // First we make a serialisable datastructure so we can encode it to + // json. + sam := new(serializedAddrManager) + sam.Version = serialisationVersion + copy(sam.Key[:], a.key[:]) + + sam.Addresses = make([]*serializedKnownAddress, len(a.addrIndex)) + i := 0 + for k, v := range a.addrIndex { + ska := new(serializedKnownAddress) + ska.Addr = k + ska.TimeStamp = v.na.Timestamp.Unix() + ska.Src = NetAddressKey(v.srcAddr) + ska.Attempts = v.attempts + ska.LastAttempt = v.lastattempt.Unix() + ska.LastSuccess = v.lastsuccess.Unix() + // Tried and refs are implicit in the rest of the structure + // and will be worked out from context on unserialisation. + sam.Addresses[i] = ska + i++ + } + for i := range a.addrNew { + sam.NewBuckets[i] = make([]string, len(a.addrNew[i])) + j := 0 + for k := range a.addrNew[i] { + sam.NewBuckets[i][j] = k + j++ + } + } + for i := range a.addrTried { + sam.TriedBuckets[i] = make([]string, a.addrTried[i].Len()) + j := 0 + for e := a.addrTried[i].Front(); e != nil; e = e.Next() { + ka := e.Value.(*KnownAddress) + sam.TriedBuckets[i][j] = NetAddressKey(ka.na) + j++ + } + } + + w, err := os.Create(a.peersFile) + if err != nil { + log.Errorf("Error opening file %s: %v", a.peersFile, err) + return + } + enc := json.NewEncoder(w) + defer w.Close() + if err := enc.Encode(&sam); err != nil { + log.Errorf("Failed to encode file %s: %v", a.peersFile, err) + return + } +} + +// loadPeers loads the known address from the saved file. If empty, missing, or +// malformed file, just don't load anything and start fresh +func (a *AddrManager) loadPeers() { + a.mtx.Lock() + defer a.mtx.Unlock() + + err := a.deserializePeers(a.peersFile) + if err != nil { + log.Errorf("Failed to parse file %s: %v", a.peersFile, err) + // if it is invalid we nuke the old one unconditionally. + err = os.Remove(a.peersFile) + if err != nil { + log.Warnf("Failed to remove corrupt peers file %s: %v", + a.peersFile, err) + } + a.reset() + return + } + log.Infof("Loaded %d addresses from file '%s'", a.numAddresses(), a.peersFile) +} + +func (a *AddrManager) deserializePeers(filePath string) error { + + _, err := os.Stat(filePath) + if os.IsNotExist(err) { + return nil + } + r, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("%s error opening file: %v", filePath, err) + } + defer r.Close() + + var sam serializedAddrManager + dec := json.NewDecoder(r) + err = dec.Decode(&sam) + if err != nil { + return fmt.Errorf("error reading %s: %v", filePath, err) + } + + if sam.Version != serialisationVersion { + return fmt.Errorf("unknown version %v in serialized "+ + "addrmanager", sam.Version) + } + copy(a.key[:], sam.Key[:]) + + for _, v := range sam.Addresses { + ka := new(KnownAddress) + ka.na, err = a.DeserializeNetAddress(v.Addr) + if err != nil { + return fmt.Errorf("failed to deserialize netaddress "+ + "%s: %v", v.Addr, err) + } + ka.srcAddr, err = a.DeserializeNetAddress(v.Src) + if err != nil { + return fmt.Errorf("failed to deserialize netaddress "+ + "%s: %v", v.Src, err) + } + ka.attempts = v.Attempts + ka.lastattempt = time.Unix(v.LastAttempt, 0) + ka.lastsuccess = time.Unix(v.LastSuccess, 0) + a.addrIndex[NetAddressKey(ka.na)] = ka + } + + for i := range sam.NewBuckets { + for _, val := range sam.NewBuckets[i] { + ka, ok := a.addrIndex[val] + if !ok { + return fmt.Errorf("new buckets contains %s but "+ + "none in address list", val) + } + + if ka.refs == 0 { + a.nNew++ + } + ka.refs++ + a.addrNew[i][val] = ka + } + } + for i := range sam.TriedBuckets { + for _, val := range sam.TriedBuckets[i] { + ka, ok := a.addrIndex[val] + if !ok { + return fmt.Errorf("tried buckets contains %s but "+ + "none in address list", val) + } + + ka.tried = true + a.nTried++ + a.addrTried[i].PushBack(ka) + } + } + + // Sanity checking. + for k, v := range a.addrIndex { + if v.refs == 0 && !v.tried { + return fmt.Errorf("address %s after serialisation "+ + "with no references", k) + } + + if v.refs > 0 && v.tried { + return fmt.Errorf("address %s after serialisation "+ + "which is both new and tried!", k) + } + } + + return nil +} + +// DeserializeNetAddress converts a given address string to a *wire.NetAddress +func (a *AddrManager) DeserializeNetAddress(addr string) (*wire.NetAddress, error) { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, err + } + + return a.HostToNetAddress(host, uint16(port), wire.SFNodeNetwork) +} + +// 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 atomic.AddInt32(&a.started, 1) != 1 { + return + } + + log.Trace("Starting address manager") + + // Load peers we already know about from file. + a.loadPeers() + + // Start the address ticker to save addresses periodically. + a.wg.Add(1) + go a.addressHandler() +} + +// Stop gracefully shuts down the address manager by stopping the main handler. +func (a *AddrManager) Stop() error { + if atomic.AddInt32(&a.shutdown, 1) != 1 { + log.Warnf("Address manager is already in the process of " + + "shutting down") + return nil + } + + log.Infof("Address manager shutting down") + close(a.quit) + 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 []*wire.NetAddress, srcAddr *wire.NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + + for _, na := range addrs { + a.updateAddress(na, srcAddr) + } +} + +// 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, srcAddr *wire.NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + + a.updateAddress(addr, srcAddr) +} + +// AddAddressByIP adds an address where we are given an ip:port and not a +// wire.NetAddress. +func (a *AddrManager) AddAddressByIP(addrIP string) error { + // Split IP and port + addr, portStr, err := net.SplitHostPort(addrIP) + if err != nil { + return err + } + // Put it in wire.Netaddress + ip := net.ParseIP(addr) + if ip == nil { + return fmt.Errorf("invalid ip address %s", addr) + } + port, err := strconv.ParseUint(portStr, 10, 0) + if err != nil { + return fmt.Errorf("invalid port %s: %v", portStr, err) + } + na := wire.NewNetAddressIPPort(ip, uint16(port), 0) + a.AddAddress(na, na) // XXX use correct src address + return nil +} + +// NumAddresses returns the number of addresses known to the address manager. +func (a *AddrManager) numAddresses() int { + return a.nTried + a.nNew +} + +// NumAddresses returns the number of addresses known to the address manager. +func (a *AddrManager) NumAddresses() int { + a.mtx.Lock() + defer a.mtx.Unlock() + + return a.numAddresses() +} + +// NeedMoreAddresses returns whether or not the address manager needs more +// addresses. +func (a *AddrManager) NeedMoreAddresses() bool { + a.mtx.Lock() + defer a.mtx.Unlock() + + return a.numAddresses() < needAddressThreshold +} + +// AddressCache returns the current address cache. It must be treated as +// read-only (but since it is a copy now, this is not as dangerous). +func (a *AddrManager) AddressCache() []*wire.NetAddress { + //a.mtx.Lock() + //defer a.mtx.Unlock() + // + //addrIndexLen := len(a.addrIndex) + //if addrIndexLen == 0 { + // return nil + //} + // + //allAddr := make([]*wire.NetAddress, 0, addrIndexLen) + //// Iteration order is undefined here, but we randomise it anyway. + //for _, v := range a.addrIndex { + // allAddr = append(allAddr, v.na) + //} + allAddr := a.getAddresses() + + //numAddresses := addrIndexLen * getAddrPercent / 100 + numAddresses := len(allAddr) * getAddrPercent / 100 + if numAddresses > getAddrMax { + numAddresses = getAddrMax + } + + // Fisher-Yates shuffle the array. We only need to do the first + // `numAddresses' since we are throwing the rest. + for i := 0; i < numAddresses; i++ { + // pick a number between current index and the end + //j := rand.Intn(addrIndexLen-i) + i + j := rand.Intn(len(allAddr)-i) + i + allAddr[i], allAddr[j] = allAddr[j], allAddr[i] + } + + // slice off the limit we are willing to share. + return allAddr[0:numAddresses] +} + +// getAddresses returns all of the addresses currently found within the +// manager's address cache. +func (a *AddrManager) getAddresses() []*wire.NetAddress { + a.mtx.Lock() + defer a.mtx.Unlock() + + addrIndexLen := len(a.addrIndex) + if addrIndexLen == 0 { + return nil + } + + addrs := make([]*wire.NetAddress, 0, addrIndexLen) + for _, v := range a.addrIndex { + addrs = append(addrs, v.na) + } + + return addrs +} + +// reset resets the address manager by reinitialising the random source +// and allocating fresh empty bucket storage. +func (a *AddrManager) reset() { + + a.addrIndex = make(map[string]*KnownAddress) + + // fill key with bytes from a good random source. + io.ReadFull(crand.Reader, a.key[:]) + for i := range a.addrNew { + a.addrNew[i] = make(map[string]*KnownAddress) + } + for i := range a.addrTried { + a.addrTried[i] = list.New() + } +} + +// HostToNetAddress returns a netaddress given a host address. If the address is +// a tor .onion address this will be taken care of. else if the host is not an +// IP address it will be resolved (via tor if required). +func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*wire.NetAddress, error) { + // tor address is 16 char base32 + ".onion" + var ip net.IP + if len(host) == 22 && host[16:] == ".onion" { + // go base32 encoding uses capitals (as does the rfc + // but tor and bitcoind tend to user lowercase, so we switch + // case here. + data, err := base32.StdEncoding.DecodeString( + strings.ToUpper(host[:16])) + if err != nil { + return nil, err + } + prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} + ip = net.IP(append(prefix, data...)) + } else if ip = net.ParseIP(host); ip == nil { + ips, err := a.lookupFunc(host) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("no addresses found for %s", host) + } + ip = ips[0] + } + + return wire.NewNetAddressIPPort(ip, port, services), nil +} + +// ipString returns a string for the ip from the provided NetAddress. If the +// ip is in the range used for tor addresses then it will be transformed into +// the relevant .onion address. +func ipString(na *wire.NetAddress) string { + if IsOnionCatTor(na) { + // We know now that na.IP is long enogh. + base32 := base32.StdEncoding.EncodeToString(na.IP[6:]) + return strings.ToLower(base32) + ".onion" + } + + return na.IP.String() +} + +// NetAddressKey returns a string key in the form of ip:port for IPv4 addresses +// or [ip]:port for IPv6 addresses. +func NetAddressKey(na *wire.NetAddress) string { + port := strconv.FormatUint(uint64(na.Port), 10) + + return net.JoinHostPort(ipString(na), port) +} + +// GetAddress returns a single address that should be routable. It picks a +// random one from the possible addresses with preference given to ones that +// have not been used recently and should not pick 'close' addresses +// consecutively. +func (a *AddrManager) GetAddress() *KnownAddress { + // Protect concurrent access. + a.mtx.Lock() + defer a.mtx.Unlock() + + if a.numAddresses() == 0 { + return nil + } + + // Use a 50% chance for choosing between tried and new table entries. + if a.nTried > 0 && (a.nNew == 0 || a.rand.Intn(2) == 0) { + // Tried entry. + large := 1 << 30 + factor := 1.0 + for { + // pick a random bucket. + bucket := a.rand.Intn(len(a.addrTried)) + if a.addrTried[bucket].Len() == 0 { + continue + } + + // Pick a random entry in the list + e := a.addrTried[bucket].Front() + for i := + a.rand.Int63n(int64(a.addrTried[bucket].Len())); i > 0; i-- { + e = e.Next() + } + ka := e.Value.(*KnownAddress) + randval := a.rand.Intn(large) + if float64(randval) < (factor * ka.chance() * float64(large)) { + log.Tracef("Selected %v from tried bucket", + NetAddressKey(ka.na)) + return ka + } + factor *= 1.2 + } + } else { + // new node. + // XXX use a closure/function to avoid repeating this. + large := 1 << 30 + factor := 1.0 + for { + // Pick a random bucket. + bucket := a.rand.Intn(len(a.addrNew)) + if len(a.addrNew[bucket]) == 0 { + continue + } + // Then, a random entry in it. + var ka *KnownAddress + nth := a.rand.Intn(len(a.addrNew[bucket])) + for _, value := range a.addrNew[bucket] { + if nth == 0 { + ka = value + } + nth-- + } + randval := a.rand.Intn(large) + if float64(randval) < (factor * ka.chance() * float64(large)) { + log.Tracef("Selected %v from new bucket", + NetAddressKey(ka.na)) + return ka + } + factor *= 1.2 + } + } +} + +func (a *AddrManager) find(addr *wire.NetAddress) *KnownAddress { + return a.addrIndex[NetAddressKey(addr)] +} + +// Attempt increases the given address' attempt counter and updates +// the last attempt time. +func (a *AddrManager) Attempt(addr *wire.NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + + // find address. + // Surely address will be in tried by now? + ka := a.find(addr) + if ka == nil { + return + } + // set last tried time to now + ka.mtx.Lock() + ka.attempts++ + ka.lastattempt = time.Now() + ka.mtx.Unlock() +} + +// Connected Marks the given address as currently connected and working at the +// current time. The address must already be known to AddrManager else it will +// be ignored. +func (a *AddrManager) Connected(addr *wire.NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + + ka := a.find(addr) + if ka == nil { + return + } + + // Update the time as long as it has been 20 minutes since last we did + // so. + now := time.Now() + if now.After(ka.na.Timestamp.Add(time.Minute * 20)) { + // ka.na is immutable, so replace it. + ka.mtx.Lock() + naCopy := *ka.na + naCopy.Timestamp = time.Now() + ka.na = &naCopy + ka.mtx.Unlock() + } +} + +// Good marks the given address as good. To be called after a successful +// connection and version exchange. If the address is unknown to the address +// manager it will be ignored. +func (a *AddrManager) Good(addr *wire.NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + + ka := a.find(addr) + if ka == nil { + return + } + + // ka.Timestamp is not updated here to avoid leaking information + // about currently connected peers. + now := time.Now() + ka.lastsuccess = now + ka.lastattempt = now + ka.attempts = 0 + + // move to tried set, optionally evicting other addresses if neeed. + if ka.tried { + return + } + + // ok, need to move it to tried. + + // remove from all new buckets. + // record one of the buckets in question and call it the `first' + addrKey := NetAddressKey(addr) + oldBucket := -1 + for i := range a.addrNew { + // we check for existence so we can record the first one + if _, ok := a.addrNew[i][addrKey]; ok { + delete(a.addrNew[i], addrKey) + ka.refs-- + if oldBucket == -1 { + oldBucket = i + } + } + } + a.nNew-- + + if oldBucket == -1 { + // What? wasn't in a bucket after all.... Panic? + return + } + + bucket := a.getTriedBucket(ka.na) + + // Room in this tried bucket? + if a.addrTried[bucket].Len() < triedBucketSize { + ka.tried = true + a.addrTried[bucket].PushBack(ka) + a.nTried++ + return + } + + // No room, we have to evict something else. + entry := a.pickTried(bucket) + rmka := entry.Value.(*KnownAddress) + + // First bucket it would have been put in. + newBucket := a.getNewBucket(rmka.na, rmka.srcAddr) + + // If no room in the original bucket, we put it in a bucket we just + // freed up a space in. + if len(a.addrNew[newBucket]) >= newBucketSize { + newBucket = oldBucket + } + + // replace with ka in list. + ka.tried = true + entry.Value = ka + + rmka.tried = false + rmka.refs++ + + // We don't touch a.nTried here since the number of tried stays the same + // but we decemented new above, raise it again since we're putting + // something back. + a.nNew++ + + rmkey := NetAddressKey(rmka.na) + log.Tracef("Replacing %s with %s in tried", rmkey, addrKey) + + // We made sure there is space here just above. + a.addrNew[newBucket][rmkey] = rmka +} + +// AddLocalAddress adds na to the list of known local addresses to advertise +// with the given priority. +func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPriority) error { + if !IsRoutable(na) { + return fmt.Errorf("address %s is not routable", na.IP) + } + + a.lamtx.Lock() + defer a.lamtx.Unlock() + + key := NetAddressKey(na) + la, ok := a.localAddresses[key] + if !ok || la.score < priority { + if ok { + la.score = priority + 1 + } else { + a.localAddresses[key] = &localAddress{ + na: na, + score: priority, + } + } + } + return nil +} + +// getReachabilityFrom returns the relative reachability of the provided local +// address to the provided remote address. +func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { + const ( + Unreachable = 0 + Default = iota + Teredo + Ipv6Weak + Ipv4 + Ipv6Strong + Private + ) + + if !IsRoutable(remoteAddr) { + return Unreachable + } + + if IsOnionCatTor(remoteAddr) { + if IsOnionCatTor(localAddr) { + return Private + } + + if IsRoutable(localAddr) && IsIPv4(localAddr) { + return Ipv4 + } + + return Default + } + + if IsRFC4380(remoteAddr) { + if !IsRoutable(localAddr) { + return Default + } + + if IsRFC4380(localAddr) { + return Teredo + } + + if IsIPv4(localAddr) { + return Ipv4 + } + + return Ipv6Weak + } + + if IsIPv4(remoteAddr) { + if IsRoutable(localAddr) && IsIPv4(localAddr) { + return Ipv4 + } + return Unreachable + } + + /* ipv6 */ + var tunnelled bool + // Is our v6 is tunnelled? + if IsRFC3964(localAddr) || IsRFC6052(localAddr) || IsRFC6145(localAddr) { + tunnelled = true + } + + if !IsRoutable(localAddr) { + return Default + } + + if IsRFC4380(localAddr) { + return Teredo + } + + if IsIPv4(localAddr) { + return Ipv4 + } + + if tunnelled { + // only prioritise ipv6 if we aren't tunnelling it. + return Ipv6Weak + } + + return Ipv6Strong +} + +// GetBestLocalAddress returns the most appropriate local address to use +// for the given remote address. +func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.NetAddress { + a.lamtx.Lock() + defer a.lamtx.Unlock() + + bestreach := 0 + var bestscore AddressPriority + var bestAddress *wire.NetAddress + for _, la := range a.localAddresses { + reach := getReachabilityFrom(la.na, remoteAddr) + if reach > bestreach || + (reach == bestreach && la.score > bestscore) { + bestreach = reach + bestscore = la.score + bestAddress = la.na + } + } + if bestAddress != nil { + log.Debugf("Suggesting address %s:%d for %s:%d", bestAddress.IP, + bestAddress.Port, remoteAddr.IP, remoteAddr.Port) + } else { + log.Debugf("No worthy address for %s:%d", remoteAddr.IP, + remoteAddr.Port) + + // Send something unroutable if nothing suitable. + var ip net.IP + if !IsIPv4(remoteAddr) && !IsOnionCatTor(remoteAddr) { + ip = net.IPv6zero + } else { + ip = net.IPv4zero + } + bestAddress = wire.NewNetAddressIPPort(ip, 0, wire.SFNodeNetwork) + } + + return bestAddress +} + +// New returns a new hcd address manager. +// Use Start to begin processing asynchronous address updates. +func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager { + am := AddrManager{ + peersFile: filepath.Join(dataDir, "peers.json"), + lookupFunc: lookupFunc, + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + quit: make(chan struct{}), + localAddresses: make(map[string]*localAddress), + } + am.reset() + return &am +} + +// SetServices sets the services for the giiven address to the provided value. +func (a *AddrManager) SetServices(addr *wire.NetAddress, services wire.ServiceFlag) { + a.mtx.Lock() + defer a.mtx.Unlock() + + ka := a.find(addr) + if ka == nil { + return + } + + // Update the services if needed. + if ka.na.Services != services { + // ka.na is immutable, so replace it. + ka.mtx.Lock() + naCopy := *ka.na + naCopy.Services = services + ka.na = &naCopy + ka.mtx.Unlock() + } +} diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index b32a89ff..7d7b736f 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -1,500 +1,499 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr_test - -import ( - "errors" - "fmt" - "net" - "reflect" - "testing" - "time" - - "github.com/HcashOrg/hcd/addrmgr" - "github.com/HcashOrg/hcd/wire" -) -// naTests houses all of the tests to be performed against the NetAddressKey -// method. -var naTests = make([]naTest, 0) - -// Put some IP in here for convenience. Points to google. -var someIP = "173.194.115.66" - -// naTest is used to describe a test to be performed against the NetAddressKey -// method. -type naTest struct { - in wire.NetAddress - want string -} - - - -// 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 addNaTest(ip string, port uint16, want string) { - nip := net.ParseIP(ip) - na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork) - test := naTest{na, want} - naTests = append(naTests, test) -} - -func lookupFunc(host string) ([]net.IP, error) { - return nil, errors.New("not implemented") -} - -func TestStartStop(t *testing.T) { - n := addrmgr.New("teststartstop", lookupFunc) - n.Start() - err := n.Stop() - if err != nil { - t.Fatalf("Address Manager failed to stop: %v", err) - } -} - -// TestAddAddressByIP tests networks add address by ip -func TestAddAddressByIP(t *testing.T) { - fmtErr := fmt.Errorf("") - addrErr := &net.AddrError{} - var tests = []struct { - addrIP string - err error - }{ - { - someIP + ":8333", - nil, - }, - { - someIP, - addrErr, - }, - { - someIP[:12] + ":8333", - fmtErr, - }, - { - someIP + ":abcd", - fmtErr, - }, - } - - amgr := addrmgr.New("testaddressbyip", nil) - for i, test := range tests { - err := amgr.AddAddressByIP(test.addrIP) - if test.err != nil && err == nil { - t.Errorf("TestGood test %d failed expected an error and got none", i) - continue - } - if test.err == nil && err != nil { - t.Errorf("TestGood test %d failed expected no error and got one", i) - continue - } - if reflect.TypeOf(err) != reflect.TypeOf(test.err) { - t.Errorf("TestGood test %d failed got %v, want %v", i, - reflect.TypeOf(err), reflect.TypeOf(test.err)) - continue - } - } -} - -// TestAddLocalAddress tests networks add local address by ip -func TestAddLocalAddress(t *testing.T) { - var tests = []struct { - address wire.NetAddress - priority addrmgr.AddressPriority - valid bool - }{ - { - wire.NetAddress{IP: net.ParseIP("192.168.0.100")}, - addrmgr.InterfacePrio, - false, - }, - { - wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, - addrmgr.InterfacePrio, - true, - }, - { - wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, - addrmgr.BoundPrio, - true, - }, - { - wire.NetAddress{IP: net.ParseIP("::1")}, - addrmgr.InterfacePrio, - false, - }, - { - wire.NetAddress{IP: net.ParseIP("fe80::1")}, - addrmgr.InterfacePrio, - false, - }, - { - wire.NetAddress{IP: net.ParseIP("2620:100::1")}, - addrmgr.InterfacePrio, - true, - }, - } - amgr := addrmgr.New("testaddlocaladdress", nil) - for x, test := range tests { - result := amgr.AddLocalAddress(&test.address, test.priority) - if result == nil && !test.valid { - t.Errorf("TestAddLocalAddress test #%d failed: %s should have "+ - "been accepted", x, test.address.IP) - continue - } - if result != nil && test.valid { - t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+ - "been accepted", x, test.address.IP) - continue - } - } -} - -func TestAttempt(t *testing.T) { - n := addrmgr.New("testattempt", lookupFunc) - - // Add a new address and get it - err := n.AddAddressByIP(someIP + ":8333") - if err != nil { - t.Fatalf("Adding address failed: %v", err) - } - ka := n.GetAddress() - - if !ka.LastAttempt().IsZero() { - t.Errorf("Address should not have attempts, but does") - } - - na := ka.NetAddress() - n.Attempt(na) - - if ka.LastAttempt().IsZero() { - t.Errorf("Address should have an attempt, but does not") - } -} - -// TestConnected tests networks connected -func TestConnected(t *testing.T) { - n := addrmgr.New("testconnected", lookupFunc) - - // Add a new address and get it - err := n.AddAddressByIP(someIP + ":8333") - if err != nil { - t.Fatalf("Adding address failed: %v", err) - } - ka := n.GetAddress() - na := ka.NetAddress() - // make it an hour ago - na.Timestamp = time.Unix(time.Now().Add(time.Hour*-1).Unix(), 0) - - n.Connected(na) - - if !ka.NetAddress().Timestamp.After(na.Timestamp) { - t.Errorf("Address should have a new timestamp, but does not") - } -} - -func TestNeedMoreAddresses(t *testing.T) { - n := addrmgr.New("testneedmoreaddresses", lookupFunc) - addrsToAdd := 1500 - b := n.NeedMoreAddresses() - if !b { - t.Errorf("Expected that we need more addresses") - } - addrs := make([]*wire.NetAddress, addrsToAdd) - - var err error - for i := 0; i < addrsToAdd; i++ { - s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60) - addrs[i], err = n.DeserializeNetAddress(s) - if err != nil { - t.Errorf("Failed to turn %s into an address: %v", s, err) - } - } - - srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) - - n.AddAddresses(addrs, srcAddr) - numAddrs := n.NumAddresses() - if numAddrs > addrsToAdd { - t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd) - } - - b = n.NeedMoreAddresses() - if b { - t.Errorf("Expected that we don't need more addresses") - } -} - -func TestGood(t *testing.T) { - n := addrmgr.New("testgood", lookupFunc) - addrsToAdd := 64 * 64 - addrs := make([]*wire.NetAddress, addrsToAdd) - - var err error - for i := 0; i < addrsToAdd; i++ { - s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60) - addrs[i], err = n.DeserializeNetAddress(s) - if err != nil { - t.Errorf("Failed to turn %s into an address: %v", s, err) - } - } - - srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) - - n.AddAddresses(addrs, srcAddr) - for _, addr := range addrs { - n.Good(addr) - } - - numAddrs := n.NumAddresses() - if numAddrs >= addrsToAdd { - t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd) - } - - numCache := len(n.AddressCache()) - if numCache >= numAddrs/4 { - t.Errorf("Number of addresses in cache: got %d, want %d", numCache, numAddrs/4) - } -} - -func TestGetAddress(t *testing.T) { - n := addrmgr.New("testgetaddress", lookupFunc) - - // Get an address from an empty set (should error) - if rv := n.GetAddress(); rv != nil { - t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil) - } - - // Add a new address and get it - err := n.AddAddressByIP(someIP + ":8333") - if err != nil { - t.Fatalf("Adding address failed: %v", err) - } - ka := n.GetAddress() - if ka == nil { - t.Fatalf("Did not get an address where there is one in the pool") - } - if ka.NetAddress().IP.String() != someIP { - t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) - } - - // Mark this as a good address and get it - n.Good(ka.NetAddress()) - ka = n.GetAddress() - if ka == nil { - t.Fatalf("Did not get an address where there is one in the pool") - } - if ka.NetAddress().IP.String() != someIP { - t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) - } - - numAddrs := n.NumAddresses() - if numAddrs != 1 { - t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1) - } -} - -// TestGetBestLocalAddress tests get best local address -func TestGetBestLocalAddress(t *testing.T) { - localAddrs := []wire.NetAddress{ - {IP: net.ParseIP("192.168.0.100")}, - {IP: net.ParseIP("::1")}, - {IP: net.ParseIP("fe80::1")}, - {IP: net.ParseIP("2001:470::1")}, - } - - var tests = []struct { - remoteAddr wire.NetAddress - want0 wire.NetAddress - want1 wire.NetAddress - want2 wire.NetAddress - want3 wire.NetAddress - }{ - { - // Remote connection from public IPv4 - wire.NetAddress{IP: net.ParseIP("204.124.8.1")}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.ParseIP("204.124.8.100")}, - wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, - }, - { - // Remote connection from private IPv4 - wire.NetAddress{IP: net.ParseIP("172.16.0.254")}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - }, - { - // Remote connection from public IPv6 - wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")}, - wire.NetAddress{IP: net.IPv6zero}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, - }, - /* XXX - { - // Remote connection from Tor - wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.ParseIP("204.124.8.100")}, - wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, - }, - */ - } - - amgr := addrmgr.New("testgetbestlocaladdress", nil) - - // Test against default when there's no address - for x, test := range tests { - got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want0.IP.Equal(got.IP) { - t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want1.IP, got.IP) - continue - } - } - - for _, localAddr := range localAddrs { - amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) - } - - // Test against want1 - for x, test := range tests { - got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want1.IP.Equal(got.IP) { - t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want1.IP, got.IP) - continue - } - } - - // Add a public IP to the list of local addresses. - localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")} - amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) - - // Test against want2 - for x, test := range tests { - got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want2.IP.Equal(got.IP) { - t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want2.IP, got.IP) - continue - } - } - /* - // Add a tor generated IP address - localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")} - amgr.AddLocalAddress(&localAddr, addrmgr.ManualPrio) - - // Test against want3 - for x, test := range tests { - got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want3.IP.Equal(got.IP) { - t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want3.IP, got.IP) - continue - } - } - */ -} - -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 - } - } -} -func TestCorruptPeersFile(t *testing.T) { - dir, err := ioutil.TempDir("", "testcorruptpeersfile") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - peersFile := filepath.Join(dir, PeersFilename) - // create corrupt (empty) peers file - fp, err := os.Create(peersFile) - if err != nil { - t.Fatalf("Could not create empty peers file: %s", peersFile) - } - if err := fp.Close(); err != nil { - t.Fatalf("Could not write empty peers file: %s", peersFile) - } - amgr := New(dir, nil) - amgr.Start() - amgr.Stop() - if _, err := os.Stat(peersFile); err != nil { - t.Fatalf("Corrupt peers file has not been removed: %s", peersFile) - } -} +// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr_test + +import ( + "errors" + "fmt" + "net" + "reflect" + "testing" + "time" + + "github.com/james-ray/hcd/addrmgr" + "github.com/james-ray/hcd/wire" +) + +// naTests houses all of the tests to be performed against the NetAddressKey +// method. +var naTests = make([]naTest, 0) + +// Put some IP in here for convenience. Points to google. +var someIP = "173.194.115.66" + +// naTest is used to describe a test to be performed against the NetAddressKey +// method. +type naTest struct { + in wire.NetAddress + want string +} + +// 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 addNaTest(ip string, port uint16, want string) { + nip := net.ParseIP(ip) + na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork) + test := naTest{na, want} + naTests = append(naTests, test) +} + +func lookupFunc(host string) ([]net.IP, error) { + return nil, errors.New("not implemented") +} + +func TestStartStop(t *testing.T) { + n := addrmgr.New("teststartstop", lookupFunc) + n.Start() + err := n.Stop() + if err != nil { + t.Fatalf("Address Manager failed to stop: %v", err) + } +} + +// TestAddAddressByIP tests networks add address by ip +func TestAddAddressByIP(t *testing.T) { + fmtErr := fmt.Errorf("") + addrErr := &net.AddrError{} + var tests = []struct { + addrIP string + err error + }{ + { + someIP + ":8333", + nil, + }, + { + someIP, + addrErr, + }, + { + someIP[:12] + ":8333", + fmtErr, + }, + { + someIP + ":abcd", + fmtErr, + }, + } + + amgr := addrmgr.New("testaddressbyip", nil) + for i, test := range tests { + err := amgr.AddAddressByIP(test.addrIP) + if test.err != nil && err == nil { + t.Errorf("TestGood test %d failed expected an error and got none", i) + continue + } + if test.err == nil && err != nil { + t.Errorf("TestGood test %d failed expected no error and got one", i) + continue + } + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("TestGood test %d failed got %v, want %v", i, + reflect.TypeOf(err), reflect.TypeOf(test.err)) + continue + } + } +} + +// TestAddLocalAddress tests networks add local address by ip +func TestAddLocalAddress(t *testing.T) { + var tests = []struct { + address wire.NetAddress + priority addrmgr.AddressPriority + valid bool + }{ + { + wire.NetAddress{IP: net.ParseIP("192.168.0.100")}, + addrmgr.InterfacePrio, + false, + }, + { + wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, + addrmgr.InterfacePrio, + true, + }, + { + wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, + addrmgr.BoundPrio, + true, + }, + { + wire.NetAddress{IP: net.ParseIP("::1")}, + addrmgr.InterfacePrio, + false, + }, + { + wire.NetAddress{IP: net.ParseIP("fe80::1")}, + addrmgr.InterfacePrio, + false, + }, + { + wire.NetAddress{IP: net.ParseIP("2620:100::1")}, + addrmgr.InterfacePrio, + true, + }, + } + amgr := addrmgr.New("testaddlocaladdress", nil) + for x, test := range tests { + result := amgr.AddLocalAddress(&test.address, test.priority) + if result == nil && !test.valid { + t.Errorf("TestAddLocalAddress test #%d failed: %s should have "+ + "been accepted", x, test.address.IP) + continue + } + if result != nil && test.valid { + t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+ + "been accepted", x, test.address.IP) + continue + } + } +} + +func TestAttempt(t *testing.T) { + n := addrmgr.New("testattempt", lookupFunc) + + // Add a new address and get it + err := n.AddAddressByIP(someIP + ":8333") + if err != nil { + t.Fatalf("Adding address failed: %v", err) + } + ka := n.GetAddress() + + if !ka.LastAttempt().IsZero() { + t.Errorf("Address should not have attempts, but does") + } + + na := ka.NetAddress() + n.Attempt(na) + + if ka.LastAttempt().IsZero() { + t.Errorf("Address should have an attempt, but does not") + } +} + +// TestConnected tests networks connected +func TestConnected(t *testing.T) { + n := addrmgr.New("testconnected", lookupFunc) + + // Add a new address and get it + err := n.AddAddressByIP(someIP + ":8333") + if err != nil { + t.Fatalf("Adding address failed: %v", err) + } + ka := n.GetAddress() + na := ka.NetAddress() + // make it an hour ago + na.Timestamp = time.Unix(time.Now().Add(time.Hour*-1).Unix(), 0) + + n.Connected(na) + + if !ka.NetAddress().Timestamp.After(na.Timestamp) { + t.Errorf("Address should have a new timestamp, but does not") + } +} + +func TestNeedMoreAddresses(t *testing.T) { + n := addrmgr.New("testneedmoreaddresses", lookupFunc) + addrsToAdd := 1500 + b := n.NeedMoreAddresses() + if !b { + t.Errorf("Expected that we need more addresses") + } + addrs := make([]*wire.NetAddress, addrsToAdd) + + var err error + for i := 0; i < addrsToAdd; i++ { + s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60) + addrs[i], err = n.DeserializeNetAddress(s) + if err != nil { + t.Errorf("Failed to turn %s into an address: %v", s, err) + } + } + + srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) + + n.AddAddresses(addrs, srcAddr) + numAddrs := n.NumAddresses() + if numAddrs > addrsToAdd { + t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd) + } + + b = n.NeedMoreAddresses() + if b { + t.Errorf("Expected that we don't need more addresses") + } +} + +func TestGood(t *testing.T) { + n := addrmgr.New("testgood", lookupFunc) + addrsToAdd := 64 * 64 + addrs := make([]*wire.NetAddress, addrsToAdd) + + var err error + for i := 0; i < addrsToAdd; i++ { + s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60) + addrs[i], err = n.DeserializeNetAddress(s) + if err != nil { + t.Errorf("Failed to turn %s into an address: %v", s, err) + } + } + + srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) + + n.AddAddresses(addrs, srcAddr) + for _, addr := range addrs { + n.Good(addr) + } + + numAddrs := n.NumAddresses() + if numAddrs >= addrsToAdd { + t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd) + } + + numCache := len(n.AddressCache()) + if numCache >= numAddrs/4 { + t.Errorf("Number of addresses in cache: got %d, want %d", numCache, numAddrs/4) + } +} + +func TestGetAddress(t *testing.T) { + n := addrmgr.New("testgetaddress", lookupFunc) + + // Get an address from an empty set (should error) + if rv := n.GetAddress(); rv != nil { + t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil) + } + + // Add a new address and get it + err := n.AddAddressByIP(someIP + ":8333") + if err != nil { + t.Fatalf("Adding address failed: %v", err) + } + ka := n.GetAddress() + if ka == nil { + t.Fatalf("Did not get an address where there is one in the pool") + } + if ka.NetAddress().IP.String() != someIP { + t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) + } + + // Mark this as a good address and get it + n.Good(ka.NetAddress()) + ka = n.GetAddress() + if ka == nil { + t.Fatalf("Did not get an address where there is one in the pool") + } + if ka.NetAddress().IP.String() != someIP { + t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) + } + + numAddrs := n.NumAddresses() + if numAddrs != 1 { + t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1) + } +} + +// TestGetBestLocalAddress tests get best local address +func TestGetBestLocalAddress(t *testing.T) { + localAddrs := []wire.NetAddress{ + {IP: net.ParseIP("192.168.0.100")}, + {IP: net.ParseIP("::1")}, + {IP: net.ParseIP("fe80::1")}, + {IP: net.ParseIP("2001:470::1")}, + } + + var tests = []struct { + remoteAddr wire.NetAddress + want0 wire.NetAddress + want1 wire.NetAddress + want2 wire.NetAddress + want3 wire.NetAddress + }{ + { + // Remote connection from public IPv4 + wire.NetAddress{IP: net.ParseIP("204.124.8.1")}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.ParseIP("204.124.8.100")}, + wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, + }, + { + // Remote connection from private IPv4 + wire.NetAddress{IP: net.ParseIP("172.16.0.254")}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.IPv4zero}, + }, + { + // Remote connection from public IPv6 + wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")}, + wire.NetAddress{IP: net.IPv6zero}, + wire.NetAddress{IP: net.ParseIP("2001:470::1")}, + wire.NetAddress{IP: net.ParseIP("2001:470::1")}, + wire.NetAddress{IP: net.ParseIP("2001:470::1")}, + }, + /* XXX + { + // Remote connection from Tor + wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")}, + wire.NetAddress{IP: net.IPv4zero}, + wire.NetAddress{IP: net.ParseIP("204.124.8.100")}, + wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, + }, + */ + } + + amgr := addrmgr.New("testgetbestlocaladdress", nil) + + // Test against default when there's no address + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want0.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want1.IP, got.IP) + continue + } + } + + for _, localAddr := range localAddrs { + amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) + } + + // Test against want1 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want1.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want1.IP, got.IP) + continue + } + } + + // Add a public IP to the list of local addresses. + localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")} + amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) + + // Test against want2 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want2.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want2.IP, got.IP) + continue + } + } + /* + // Add a tor generated IP address + localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")} + amgr.AddLocalAddress(&localAddr, addrmgr.ManualPrio) + + // Test against want3 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want3.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want3.IP, got.IP) + continue + } + } + */ +} + +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 + } + } +} +func TestCorruptPeersFile(t *testing.T) { + dir, err := ioutil.TempDir("", "testcorruptpeersfile") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + peersFile := filepath.Join(dir, PeersFilename) + // create corrupt (empty) peers file + fp, err := os.Create(peersFile) + if err != nil { + t.Fatalf("Could not create empty peers file: %s", peersFile) + } + if err := fp.Close(); err != nil { + t.Fatalf("Could not write empty peers file: %s", peersFile) + } + amgr := New(dir, nil) + amgr.Start() + amgr.Stop() + if _, err := os.Stat(peersFile); err != nil { + t.Fatalf("Corrupt peers file has not been removed: %s", peersFile) + } +} diff --git a/addrmgr/internal_test.go b/addrmgr/internal_test.go index dac25607..e5e84cd9 100644 --- a/addrmgr/internal_test.go +++ b/addrmgr/internal_test.go @@ -1,27 +1,27 @@ -// Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr - -import ( - "time" - - "github.com/HcashOrg/hcd/wire" -) - -func TstKnownAddressIsBad(ka *KnownAddress) bool { - return ka.isBad() -} - -func TstKnownAddressChance(ka *KnownAddress) float64 { - return ka.chance() -} - -func TstNewKnownAddress(na *wire.NetAddress, attempts int, - lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress { - return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt, - lastsuccess: lastsuccess, tried: tried, refs: refs} -} +// Copyright (c) 2013-2015 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "time" + + "github.com/james-ray/hcd/wire" +) + +func TstKnownAddressIsBad(ka *KnownAddress) bool { + return ka.isBad() +} + +func TstKnownAddressChance(ka *KnownAddress) float64 { + return ka.chance() +} + +func TstNewKnownAddress(na *wire.NetAddress, attempts int, + lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress { + return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt, + lastsuccess: lastsuccess, tried: tried, refs: refs} +} diff --git a/addrmgr/knownaddress.go b/addrmgr/knownaddress.go index 8ac8b685..ffbc525d 100644 --- a/addrmgr/knownaddress.go +++ b/addrmgr/knownaddress.go @@ -1,119 +1,119 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr - -import ( - "sync" - "time" - - "github.com/HcashOrg/hcd/wire" -) - -// KnownAddress tracks information about a known network address that is used -// to determine how viable an address is. -type KnownAddress struct { - mtx sync.Mutex - na *wire.NetAddress - srcAddr *wire.NetAddress - attempts int - lastattempt time.Time - lastsuccess time.Time - tried bool - refs int // reference count of new buckets -} - -// NetAddress returns the underlying wire.NetAddress associated with the -// known address. -func (ka *KnownAddress) NetAddress() *wire.NetAddress { - ka.mtx.Lock() - defer ka.mtx.Unlock() - return ka.na -} - -// LastAttempt returns the last time the known address was attempted. -func (ka *KnownAddress) LastAttempt() time.Time { - ka.mtx.Lock() - defer ka.mtx.Unlock() - return ka.lastattempt -} - -// chance returns the selection probability for a known address. The priority -// depends upon how recently the address has been seen, how recently it was last -// attempted and how often attempts to connect to it have failed. -func (ka *KnownAddress) chance() float64 { - ka.mtx.Lock() - defer ka.mtx.Unlock() - now := time.Now() - lastSeen := now.Sub(ka.na.Timestamp) - lastAttempt := now.Sub(ka.lastattempt) - - if lastSeen < 0 { - lastSeen = 0 - } - if lastAttempt < 0 { - lastAttempt = 0 - } - - c := 1.0 - - // Very recent attempts are less likely to be retried. - if lastAttempt < 10*time.Minute { - c *= 0.01 - } - - // Failed attempts deprioritise. - for i := ka.attempts; i > 0; i-- { - c /= 1.5 - } - - return c -} - -// isBad returns true if the address in question has not been tried in the last -// minute and meets one of the following criteria: -// 1) It claims to be from the future -// 2) It hasn't been seen in over a month -// 3) It has failed at least three times and never succeeded -// 4) It has failed ten times in the last week -// All addresses that meet these criteria are assumed to be worthless and not -// worth keeping hold of. -func (ka *KnownAddress) isBad() bool { - ka.mtx.Lock() - defer ka.mtx.Unlock() - - if ka.lastattempt.After(time.Now().Add(-1 * time.Minute)) { - return false - } - - // From the future? - if ka.na.Timestamp.After(time.Now().Add(10 * time.Minute)) { - return true - } - - // Over a month old? - if ka.na.Timestamp.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { - return true - } - - // Never succeeded? - if ka.lastsuccess.IsZero() && ka.attempts >= numRetries { - return true - } - - // Hasn't succeeded in too long? - if !ka.lastsuccess.After(time.Now().Add(-1*minBadDays*time.Hour*24)) && - ka.attempts >= maxFailures { - return true - } - - return false -} -// Services returns the services supported by the peer with the known address. -func (ka *KnownAddress) Services() wire.ServiceFlag { - return ka.na.Services -} - +// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "sync" + "time" + + "github.com/james-ray/hcd/wire" +) + +// KnownAddress tracks information about a known network address that is used +// to determine how viable an address is. +type KnownAddress struct { + mtx sync.Mutex + na *wire.NetAddress + srcAddr *wire.NetAddress + attempts int + lastattempt time.Time + lastsuccess time.Time + tried bool + refs int // reference count of new buckets +} + +// NetAddress returns the underlying wire.NetAddress associated with the +// known address. +func (ka *KnownAddress) NetAddress() *wire.NetAddress { + ka.mtx.Lock() + defer ka.mtx.Unlock() + return ka.na +} + +// LastAttempt returns the last time the known address was attempted. +func (ka *KnownAddress) LastAttempt() time.Time { + ka.mtx.Lock() + defer ka.mtx.Unlock() + return ka.lastattempt +} + +// chance returns the selection probability for a known address. The priority +// depends upon how recently the address has been seen, how recently it was last +// attempted and how often attempts to connect to it have failed. +func (ka *KnownAddress) chance() float64 { + ka.mtx.Lock() + defer ka.mtx.Unlock() + now := time.Now() + lastSeen := now.Sub(ka.na.Timestamp) + lastAttempt := now.Sub(ka.lastattempt) + + if lastSeen < 0 { + lastSeen = 0 + } + if lastAttempt < 0 { + lastAttempt = 0 + } + + c := 1.0 + + // Very recent attempts are less likely to be retried. + if lastAttempt < 10*time.Minute { + c *= 0.01 + } + + // Failed attempts deprioritise. + for i := ka.attempts; i > 0; i-- { + c /= 1.5 + } + + return c +} + +// isBad returns true if the address in question has not been tried in the last +// minute and meets one of the following criteria: +// 1) It claims to be from the future +// 2) It hasn't been seen in over a month +// 3) It has failed at least three times and never succeeded +// 4) It has failed ten times in the last week +// All addresses that meet these criteria are assumed to be worthless and not +// worth keeping hold of. +func (ka *KnownAddress) isBad() bool { + ka.mtx.Lock() + defer ka.mtx.Unlock() + + if ka.lastattempt.After(time.Now().Add(-1 * time.Minute)) { + return false + } + + // From the future? + if ka.na.Timestamp.After(time.Now().Add(10 * time.Minute)) { + return true + } + + // Over a month old? + if ka.na.Timestamp.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { + return true + } + + // Never succeeded? + if ka.lastsuccess.IsZero() && ka.attempts >= numRetries { + return true + } + + // Hasn't succeeded in too long? + if !ka.lastsuccess.After(time.Now().Add(-1*minBadDays*time.Hour*24)) && + ka.attempts >= maxFailures { + return true + } + + return false +} + +// Services returns the services supported by the peer with the known address. +func (ka *KnownAddress) Services() wire.ServiceFlag { + return ka.na.Services +} diff --git a/addrmgr/knownaddress_test.go b/addrmgr/knownaddress_test.go index d5f965b2..499a7bdc 100644 --- a/addrmgr/knownaddress_test.go +++ b/addrmgr/knownaddress_test.go @@ -1,116 +1,116 @@ -// Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr_test - -import ( - "math" - "testing" - "time" - - "github.com/HcashOrg/hcd/addrmgr" - "github.com/HcashOrg/hcd/wire" -) - -func TestChance(t *testing.T) { - now := time.Unix(time.Now().Unix(), 0) - var tests = []struct { - addr *addrmgr.KnownAddress - expected float64 - }{ - { - //Test normal case - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, - 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), - 1.0, - }, { - //Test case in which lastseen < 0 - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)}, - 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), - 1.0, - }, { - //Test case in which lastattempt < 0 - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, - 0, time.Now().Add(30*time.Minute), time.Now(), false, 0), - 1.0 * .01, - }, { - //Test case in which lastattempt < ten minutes - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, - 0, time.Now().Add(-5*time.Minute), time.Now(), false, 0), - 1.0 * .01, - }, { - //Test case with several failed attempts. - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, - 2, time.Now().Add(-30*time.Minute), time.Now(), false, 0), - 1 / 1.5 / 1.5, - }, - } - - err := .0001 - for i, test := range tests { - chance := addrmgr.TstKnownAddressChance(test.addr) - if math.Abs(test.expected-chance) >= err { - t.Errorf("err case %d: got %f, expected %f", i, chance, test.expected) - } - } -} - -func TestIsBad(t *testing.T) { - now := time.Unix(time.Now().Unix(), 0) - future := now.Add(35 * time.Minute) - monthOld := now.Add(-43 * time.Hour * 24) - secondsOld := now.Add(-2 * time.Second) - minutesOld := now.Add(-27 * time.Minute) - hoursOld := now.Add(-5 * time.Hour) - zeroTime := time.Time{} - - futureNa := &wire.NetAddress{Timestamp: future} - minutesOldNa := &wire.NetAddress{Timestamp: minutesOld} - monthOldNa := &wire.NetAddress{Timestamp: monthOld} - currentNa := &wire.NetAddress{Timestamp: secondsOld} - - //Test addresses that have been tried in the last minute. - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) { - t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.") - } - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) { - t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.") - } - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) { - t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.") - } - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) { - t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.") - } - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) { - t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.") - } - - //Test address that claims to be from the future. - if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) { - t.Errorf("test case 6: addresses that claim to be from the future are bad.") - } - - //Test address that has not been seen in over a month. - if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) { - t.Errorf("test case 7: addresses more than a month old are bad.") - } - - //It has failed at least three times and never succeeded. - if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) { - t.Errorf("test case 8: addresses that have never succeeded are bad.") - } - - //It has failed ten times in the last week - if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) { - t.Errorf("test case 9: addresses that have not succeeded in too long are bad.") - } - - //Test an address that should work. - if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) { - t.Errorf("test case 10: This should be a valid address.") - } -} +// Copyright (c) 2013-2015 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr_test + +import ( + "math" + "testing" + "time" + + "github.com/james-ray/hcd/addrmgr" + "github.com/james-ray/hcd/wire" +) + +func TestChance(t *testing.T) { + now := time.Unix(time.Now().Unix(), 0) + var tests = []struct { + addr *addrmgr.KnownAddress + expected float64 + }{ + { + //Test normal case + addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), + 1.0, + }, { + //Test case in which lastseen < 0 + addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)}, + 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), + 1.0, + }, { + //Test case in which lastattempt < 0 + addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + 0, time.Now().Add(30*time.Minute), time.Now(), false, 0), + 1.0 * .01, + }, { + //Test case in which lastattempt < ten minutes + addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + 0, time.Now().Add(-5*time.Minute), time.Now(), false, 0), + 1.0 * .01, + }, { + //Test case with several failed attempts. + addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + 2, time.Now().Add(-30*time.Minute), time.Now(), false, 0), + 1 / 1.5 / 1.5, + }, + } + + err := .0001 + for i, test := range tests { + chance := addrmgr.TstKnownAddressChance(test.addr) + if math.Abs(test.expected-chance) >= err { + t.Errorf("err case %d: got %f, expected %f", i, chance, test.expected) + } + } +} + +func TestIsBad(t *testing.T) { + now := time.Unix(time.Now().Unix(), 0) + future := now.Add(35 * time.Minute) + monthOld := now.Add(-43 * time.Hour * 24) + secondsOld := now.Add(-2 * time.Second) + minutesOld := now.Add(-27 * time.Minute) + hoursOld := now.Add(-5 * time.Hour) + zeroTime := time.Time{} + + futureNa := &wire.NetAddress{Timestamp: future} + minutesOldNa := &wire.NetAddress{Timestamp: minutesOld} + monthOldNa := &wire.NetAddress{Timestamp: monthOld} + currentNa := &wire.NetAddress{Timestamp: secondsOld} + + //Test addresses that have been tried in the last minute. + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) { + t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.") + } + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) { + t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.") + } + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) { + t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.") + } + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) { + t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.") + } + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) { + t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.") + } + + //Test address that claims to be from the future. + if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) { + t.Errorf("test case 6: addresses that claim to be from the future are bad.") + } + + //Test address that has not been seen in over a month. + if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) { + t.Errorf("test case 7: addresses more than a month old are bad.") + } + + //It has failed at least three times and never succeeded. + if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) { + t.Errorf("test case 8: addresses that have never succeeded are bad.") + } + + //It has failed ten times in the last week + if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) { + t.Errorf("test case 9: addresses that have not succeeded in too long are bad.") + } + + //Test an address that should work. + if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) { + t.Errorf("test case 10: This should be a valid address.") + } +} diff --git a/addrmgr/network.go b/addrmgr/network.go index feaadb37..967034dd 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -1,283 +1,283 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr - -import ( - "fmt" - "net" - - "github.com/HcashOrg/hcd/wire" -) - -var ( - // rfc1918Nets specifies the IPv4 private address blocks as defined by - // by RFC1918 (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16). - rfc1918Nets = []net.IPNet{ - ipNet("10.0.0.0", 8, 32), - ipNet("172.16.0.0", 12, 32), - ipNet("192.168.0.0", 16, 32), - } - - // rfc2544Net specifies the the IPv4 block as defined by RFC2544 - // (198.18.0.0/15) - rfc2544Net = ipNet("198.18.0.0", 15, 32) - - // rfc3849Net specifies the IPv6 documentation address block as defined - // by RFC3849 (2001:DB8::/32). - rfc3849Net = ipNet("2001:DB8::", 32, 128) - - // rfc3927Net specifies the IPv4 auto configuration address block as - // defined by RFC3927 (169.254.0.0/16). - rfc3927Net = ipNet("169.254.0.0", 16, 32) - - // rfc3964Net specifies the IPv6 to IPv4 encapsulation address block as - // defined by RFC3964 (2002::/16). - rfc3964Net = ipNet("2002::", 16, 128) - - // rfc4193Net specifies the IPv6 unique local address block as defined - // by RFC4193 (FC00::/7). - rfc4193Net = ipNet("FC00::", 7, 128) - - // rfc4380Net specifies the IPv6 teredo tunneling over UDP address block - // as defined by RFC4380 (2001::/32). - rfc4380Net = ipNet("2001::", 32, 128) - - // rfc4843Net specifies the IPv6 ORCHID address block as defined by - // RFC4843 (2001:10::/28). - rfc4843Net = ipNet("2001:10::", 28, 128) - - // rfc4862Net specifies the IPv6 stateless address autoconfiguration - // address block as defined by RFC4862 (FE80::/64). - rfc4862Net = ipNet("FE80::", 64, 128) - - // rfc5737Net specifies the IPv4 documentation address blocks as defined - // by RFC5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) - rfc5737Net = []net.IPNet{ - ipNet("192.0.2.0", 24, 32), - ipNet("198.51.100.0", 24, 32), - ipNet("203.0.113.0", 24, 32), - } - - // rfc6052Net specifies the IPv6 well-known prefix address block as - // defined by RFC6052 (64:FF9B::/96). - rfc6052Net = ipNet("64:FF9B::", 96, 128) - - // rfc6145Net specifies the IPv6 to IPv4 translated address range as - // defined by RFC6145 (::FFFF:0:0:0/96). - rfc6145Net = ipNet("::FFFF:0:0:0", 96, 128) - - // rfc6598Net specifies the IPv4 block as defined by RFC6598 (100.64.0.0/10) - rfc6598Net = ipNet("100.64.0.0", 10, 32) - - // onionCatNet defines the IPv6 address block used to support Tor. - // bitcoind encodes a .onion address as a 16 byte number by decoding the - // address prior to the .onion (i.e. the key hash) base32 into a ten - // byte number. It then stores the first 6 bytes of the address as - // 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43. - // - // This is the same range used by OnionCat, which is part part of the - // RFC4193 unique local IPv6 range. - // - // In summary the format is: - // { magic 6 bytes, 10 bytes base32 decode of key hash } - onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128) - - // zero4Net defines the IPv4 address block for address staring with 0 - // (0.0.0.0/8). - zero4Net = ipNet("0.0.0.0", 8, 32) - - // heNet defines the Hurricane Electric IPv6 address block. - heNet = ipNet("2001:470::", 32, 128) -) - -// ipNet returns a net.IPNet struct given the passed IP address string, number -// of one bits to include at the start of the mask, and the total number of bits -// for the mask. -func ipNet(ip string, ones, bits int) net.IPNet { - return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)} -} - -// IsIPv4 returns whether or not the given address is an IPv4 address. -func IsIPv4(na *wire.NetAddress) bool { - return na.IP.To4() != nil -} - -// IsLocal returns whether or not the given address is a local address. -func IsLocal(na *wire.NetAddress) bool { - return na.IP.IsLoopback() || zero4Net.Contains(na.IP) -} - -// IsOnionCatTor returns whether or not the passed address is in the IPv6 range -// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range -// is the same range used by OnionCat, which is part of the RFC4193 unique local -// IPv6 range. -func IsOnionCatTor(na *wire.NetAddress) bool { - return onionCatNet.Contains(na.IP) -} - -// IsRFC1918 returns whether or not the passed address is part of the IPv4 -// private network address space as defined by RFC1918 (10.0.0.0/8, -// 172.16.0.0/12, or 192.168.0.0/16). -func IsRFC1918(na *wire.NetAddress) bool { - for _, rfc := range rfc1918Nets { - if rfc.Contains(na.IP) { - return true - } - } - return false -} - -// IsRFC2544 returns whether or not the passed address is part of the IPv4 -// address space as defined by RFC2544 (198.18.0.0/15) -func IsRFC2544(na *wire.NetAddress) bool { - return rfc2544Net.Contains(na.IP) -} - -// IsRFC3849 returns whether or not the passed address is part of the IPv6 -// documentation range as defined by RFC3849 (2001:DB8::/32). -func IsRFC3849(na *wire.NetAddress) bool { - return rfc3849Net.Contains(na.IP) -} - -// IsRFC3927 returns whether or not the passed address is part of the IPv4 -// autoconfiguration range as defined by RFC3927 (169.254.0.0/16). -func IsRFC3927(na *wire.NetAddress) bool { - return rfc3927Net.Contains(na.IP) -} - -// IsRFC3964 returns whether or not the passed address is part of the IPv6 to -// IPv4 encapsulation range as defined by RFC3964 (2002::/16). -func IsRFC3964(na *wire.NetAddress) bool { - return rfc3964Net.Contains(na.IP) -} - -// IsRFC4193 returns whether or not the passed address is part of the IPv6 -// unique local range as defined by RFC4193 (FC00::/7). -func IsRFC4193(na *wire.NetAddress) bool { - return rfc4193Net.Contains(na.IP) -} - -// IsRFC4380 returns whether or not the passed address is part of the IPv6 -// teredo tunneling over UDP range as defined by RFC4380 (2001::/32). -func IsRFC4380(na *wire.NetAddress) bool { - return rfc4380Net.Contains(na.IP) -} - -// IsRFC4843 returns whether or not the passed address is part of the IPv6 -// ORCHID range as defined by RFC4843 (2001:10::/28). -func IsRFC4843(na *wire.NetAddress) bool { - return rfc4843Net.Contains(na.IP) -} - -// IsRFC4862 returns whether or not the passed address is part of the IPv6 -// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64). -func IsRFC4862(na *wire.NetAddress) bool { - return rfc4862Net.Contains(na.IP) -} - -// IsRFC5737 returns whether or not the passed address is part of the IPv4 -// documentation address space as defined by RFC5737 (192.0.2.0/24, -// 198.51.100.0/24, 203.0.113.0/24) -func IsRFC5737(na *wire.NetAddress) bool { - for _, rfc := range rfc5737Net { - if rfc.Contains(na.IP) { - return true - } - } - - return false -} - -// IsRFC6052 returns whether or not the passed address is part of the IPv6 -// well-known prefix range as defined by RFC6052 (64:FF9B::/96). -func IsRFC6052(na *wire.NetAddress) bool { - return rfc6052Net.Contains(na.IP) -} - -// IsRFC6145 returns whether or not the passed address is part of the IPv6 to -// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96). -func IsRFC6145(na *wire.NetAddress) bool { - return rfc6145Net.Contains(na.IP) -} - -// IsRFC6598 returns whether or not the passed address is part of the IPv4 -// shared address space specified by RFC6598 (100.64.0.0/10) -func IsRFC6598(na *wire.NetAddress) bool { - return rfc6598Net.Contains(na.IP) -} - -// IsValid returns whether or not the passed address is valid. The address is -// considered invalid under the following circumstances: -// IPv4: It is either a zero or all bits set address. -// IPv6: It is either a zero or RFC3849 documentation address. -func IsValid(na *wire.NetAddress) bool { - // IsUnspecified returns if address is 0, so only all bits set, and - // RFC3849 need to be explicitly checked. - return na.IP != nil && !(na.IP.IsUnspecified() || - na.IP.Equal(net.IPv4bcast)) -} - -// IsRoutable returns whether or not the passed address is routable over -// the public internet. This is true as long as the address is valid and is not -// in any reserved ranges. -func IsRoutable(na *wire.NetAddress) bool { - return IsValid(na) && !(IsRFC1918(na) || IsRFC2544(na) || - IsRFC3927(na) || IsRFC4862(na) || IsRFC3849(na) || - IsRFC4843(na) || IsRFC5737(na) || IsRFC6598(na) || - IsLocal(na) || (IsRFC4193(na) && !IsOnionCatTor(na))) -} - -// GroupKey returns a string representing the network group an address is part -// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string -// "local" for a local address, the string "tor:key" where key is the /4 of the -// onion address for tor address, and the string "unroutable" for an unroutable -// address. -func GroupKey(na *wire.NetAddress) string { - if IsLocal(na) { - return "local" - } - if !IsRoutable(na) { - return "unroutable" - } - if IsIPv4(na) { - return na.IP.Mask(net.CIDRMask(16, 32)).String() - } - if IsRFC6145(na) || IsRFC6052(na) { - // last four bytes are the ip address - ip := na.IP[12:16] - return ip.Mask(net.CIDRMask(16, 32)).String() - } - - if IsRFC3964(na) { - ip := na.IP[2:6] - return ip.Mask(net.CIDRMask(16, 32)).String() - - } - if IsRFC4380(na) { - // teredo tunnels have the last 4 bytes as the v4 address XOR - // 0xff. - ip := net.IP(make([]byte, 4)) - for i, byte := range na.IP[12:16] { - ip[i] = byte ^ 0xff - } - return ip.Mask(net.CIDRMask(16, 32)).String() - } - if IsOnionCatTor(na) { - // group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) - } - - // OK, so now we know ourselves to be a IPv6 address. - // bitcoind uses /32 for everything, except for Hurricane Electric's - // (he.net) IP range, which it uses /36 for. - bits := 32 - if heNet.Contains(na.IP) { - bits = 36 - } - - return na.IP.Mask(net.CIDRMask(bits, 128)).String() -} +// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "fmt" + "net" + + "github.com/james-ray/hcd/wire" +) + +var ( + // rfc1918Nets specifies the IPv4 private address blocks as defined by + // by RFC1918 (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16). + rfc1918Nets = []net.IPNet{ + ipNet("10.0.0.0", 8, 32), + ipNet("172.16.0.0", 12, 32), + ipNet("192.168.0.0", 16, 32), + } + + // rfc2544Net specifies the the IPv4 block as defined by RFC2544 + // (198.18.0.0/15) + rfc2544Net = ipNet("198.18.0.0", 15, 32) + + // rfc3849Net specifies the IPv6 documentation address block as defined + // by RFC3849 (2001:DB8::/32). + rfc3849Net = ipNet("2001:DB8::", 32, 128) + + // rfc3927Net specifies the IPv4 auto configuration address block as + // defined by RFC3927 (169.254.0.0/16). + rfc3927Net = ipNet("169.254.0.0", 16, 32) + + // rfc3964Net specifies the IPv6 to IPv4 encapsulation address block as + // defined by RFC3964 (2002::/16). + rfc3964Net = ipNet("2002::", 16, 128) + + // rfc4193Net specifies the IPv6 unique local address block as defined + // by RFC4193 (FC00::/7). + rfc4193Net = ipNet("FC00::", 7, 128) + + // rfc4380Net specifies the IPv6 teredo tunneling over UDP address block + // as defined by RFC4380 (2001::/32). + rfc4380Net = ipNet("2001::", 32, 128) + + // rfc4843Net specifies the IPv6 ORCHID address block as defined by + // RFC4843 (2001:10::/28). + rfc4843Net = ipNet("2001:10::", 28, 128) + + // rfc4862Net specifies the IPv6 stateless address autoconfiguration + // address block as defined by RFC4862 (FE80::/64). + rfc4862Net = ipNet("FE80::", 64, 128) + + // rfc5737Net specifies the IPv4 documentation address blocks as defined + // by RFC5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) + rfc5737Net = []net.IPNet{ + ipNet("192.0.2.0", 24, 32), + ipNet("198.51.100.0", 24, 32), + ipNet("203.0.113.0", 24, 32), + } + + // rfc6052Net specifies the IPv6 well-known prefix address block as + // defined by RFC6052 (64:FF9B::/96). + rfc6052Net = ipNet("64:FF9B::", 96, 128) + + // rfc6145Net specifies the IPv6 to IPv4 translated address range as + // defined by RFC6145 (::FFFF:0:0:0/96). + rfc6145Net = ipNet("::FFFF:0:0:0", 96, 128) + + // rfc6598Net specifies the IPv4 block as defined by RFC6598 (100.64.0.0/10) + rfc6598Net = ipNet("100.64.0.0", 10, 32) + + // onionCatNet defines the IPv6 address block used to support Tor. + // bitcoind encodes a .onion address as a 16 byte number by decoding the + // address prior to the .onion (i.e. the key hash) base32 into a ten + // byte number. It then stores the first 6 bytes of the address as + // 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43. + // + // This is the same range used by OnionCat, which is part part of the + // RFC4193 unique local IPv6 range. + // + // In summary the format is: + // { magic 6 bytes, 10 bytes base32 decode of key hash } + onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128) + + // zero4Net defines the IPv4 address block for address staring with 0 + // (0.0.0.0/8). + zero4Net = ipNet("0.0.0.0", 8, 32) + + // heNet defines the Hurricane Electric IPv6 address block. + heNet = ipNet("2001:470::", 32, 128) +) + +// ipNet returns a net.IPNet struct given the passed IP address string, number +// of one bits to include at the start of the mask, and the total number of bits +// for the mask. +func ipNet(ip string, ones, bits int) net.IPNet { + return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)} +} + +// IsIPv4 returns whether or not the given address is an IPv4 address. +func IsIPv4(na *wire.NetAddress) bool { + return na.IP.To4() != nil +} + +// IsLocal returns whether or not the given address is a local address. +func IsLocal(na *wire.NetAddress) bool { + return na.IP.IsLoopback() || zero4Net.Contains(na.IP) +} + +// IsOnionCatTor returns whether or not the passed address is in the IPv6 range +// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range +// is the same range used by OnionCat, which is part of the RFC4193 unique local +// IPv6 range. +func IsOnionCatTor(na *wire.NetAddress) bool { + return onionCatNet.Contains(na.IP) +} + +// IsRFC1918 returns whether or not the passed address is part of the IPv4 +// private network address space as defined by RFC1918 (10.0.0.0/8, +// 172.16.0.0/12, or 192.168.0.0/16). +func IsRFC1918(na *wire.NetAddress) bool { + for _, rfc := range rfc1918Nets { + if rfc.Contains(na.IP) { + return true + } + } + return false +} + +// IsRFC2544 returns whether or not the passed address is part of the IPv4 +// address space as defined by RFC2544 (198.18.0.0/15) +func IsRFC2544(na *wire.NetAddress) bool { + return rfc2544Net.Contains(na.IP) +} + +// IsRFC3849 returns whether or not the passed address is part of the IPv6 +// documentation range as defined by RFC3849 (2001:DB8::/32). +func IsRFC3849(na *wire.NetAddress) bool { + return rfc3849Net.Contains(na.IP) +} + +// IsRFC3927 returns whether or not the passed address is part of the IPv4 +// autoconfiguration range as defined by RFC3927 (169.254.0.0/16). +func IsRFC3927(na *wire.NetAddress) bool { + return rfc3927Net.Contains(na.IP) +} + +// IsRFC3964 returns whether or not the passed address is part of the IPv6 to +// IPv4 encapsulation range as defined by RFC3964 (2002::/16). +func IsRFC3964(na *wire.NetAddress) bool { + return rfc3964Net.Contains(na.IP) +} + +// IsRFC4193 returns whether or not the passed address is part of the IPv6 +// unique local range as defined by RFC4193 (FC00::/7). +func IsRFC4193(na *wire.NetAddress) bool { + return rfc4193Net.Contains(na.IP) +} + +// IsRFC4380 returns whether or not the passed address is part of the IPv6 +// teredo tunneling over UDP range as defined by RFC4380 (2001::/32). +func IsRFC4380(na *wire.NetAddress) bool { + return rfc4380Net.Contains(na.IP) +} + +// IsRFC4843 returns whether or not the passed address is part of the IPv6 +// ORCHID range as defined by RFC4843 (2001:10::/28). +func IsRFC4843(na *wire.NetAddress) bool { + return rfc4843Net.Contains(na.IP) +} + +// IsRFC4862 returns whether or not the passed address is part of the IPv6 +// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64). +func IsRFC4862(na *wire.NetAddress) bool { + return rfc4862Net.Contains(na.IP) +} + +// IsRFC5737 returns whether or not the passed address is part of the IPv4 +// documentation address space as defined by RFC5737 (192.0.2.0/24, +// 198.51.100.0/24, 203.0.113.0/24) +func IsRFC5737(na *wire.NetAddress) bool { + for _, rfc := range rfc5737Net { + if rfc.Contains(na.IP) { + return true + } + } + + return false +} + +// IsRFC6052 returns whether or not the passed address is part of the IPv6 +// well-known prefix range as defined by RFC6052 (64:FF9B::/96). +func IsRFC6052(na *wire.NetAddress) bool { + return rfc6052Net.Contains(na.IP) +} + +// IsRFC6145 returns whether or not the passed address is part of the IPv6 to +// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96). +func IsRFC6145(na *wire.NetAddress) bool { + return rfc6145Net.Contains(na.IP) +} + +// IsRFC6598 returns whether or not the passed address is part of the IPv4 +// shared address space specified by RFC6598 (100.64.0.0/10) +func IsRFC6598(na *wire.NetAddress) bool { + return rfc6598Net.Contains(na.IP) +} + +// IsValid returns whether or not the passed address is valid. The address is +// considered invalid under the following circumstances: +// IPv4: It is either a zero or all bits set address. +// IPv6: It is either a zero or RFC3849 documentation address. +func IsValid(na *wire.NetAddress) bool { + // IsUnspecified returns if address is 0, so only all bits set, and + // RFC3849 need to be explicitly checked. + return na.IP != nil && !(na.IP.IsUnspecified() || + na.IP.Equal(net.IPv4bcast)) +} + +// IsRoutable returns whether or not the passed address is routable over +// the public internet. This is true as long as the address is valid and is not +// in any reserved ranges. +func IsRoutable(na *wire.NetAddress) bool { + return IsValid(na) && !(IsRFC1918(na) || IsRFC2544(na) || + IsRFC3927(na) || IsRFC4862(na) || IsRFC3849(na) || + IsRFC4843(na) || IsRFC5737(na) || IsRFC6598(na) || + IsLocal(na) || (IsRFC4193(na) && !IsOnionCatTor(na))) +} + +// GroupKey returns a string representing the network group an address is part +// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string +// "local" for a local address, the string "tor:key" where key is the /4 of the +// onion address for tor address, and the string "unroutable" for an unroutable +// address. +func GroupKey(na *wire.NetAddress) string { + if IsLocal(na) { + return "local" + } + if !IsRoutable(na) { + return "unroutable" + } + if IsIPv4(na) { + return na.IP.Mask(net.CIDRMask(16, 32)).String() + } + if IsRFC6145(na) || IsRFC6052(na) { + // last four bytes are the ip address + ip := na.IP[12:16] + return ip.Mask(net.CIDRMask(16, 32)).String() + } + + if IsRFC3964(na) { + ip := na.IP[2:6] + return ip.Mask(net.CIDRMask(16, 32)).String() + + } + if IsRFC4380(na) { + // teredo tunnels have the last 4 bytes as the v4 address XOR + // 0xff. + ip := net.IP(make([]byte, 4)) + for i, byte := range na.IP[12:16] { + ip[i] = byte ^ 0xff + } + return ip.Mask(net.CIDRMask(16, 32)).String() + } + if IsOnionCatTor(na) { + // group is keyed off the first 4 bits of the actual onion key. + return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) + } + + // OK, so now we know ourselves to be a IPv6 address. + // bitcoind uses /32 for everything, except for Hurricane Electric's + // (he.net) IP range, which it uses /36 for. + bits := 32 + if heNet.Contains(na.IP) { + bits = 36 + } + + return na.IP.Mask(net.CIDRMask(bits, 128)).String() +} diff --git a/addrmgr/network_test.go b/addrmgr/network_test.go index 00014543..fb954597 100644 --- a/addrmgr/network_test.go +++ b/addrmgr/network_test.go @@ -1,202 +1,202 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package addrmgr_test - -import ( - "net" - "testing" - - "github.com/HcashOrg/hcd/addrmgr" - "github.com/HcashOrg/hcd/wire" -) - -// TestIPTypes ensures the various functions which determine the type of an IP -// address based on RFCs work as intended. -func TestIPTypes(t *testing.T) { - type ipTest struct { - in wire.NetAddress - rfc1918 bool - rfc2544 bool - rfc3849 bool - rfc3927 bool - rfc3964 bool - rfc4193 bool - rfc4380 bool - rfc4843 bool - rfc4862 bool - rfc5737 bool - rfc6052 bool - rfc6145 bool - rfc6598 bool - local bool - valid bool - routable bool - } - - newIPTest := func(ip string, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, - rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, - local, valid, routable bool) ipTest { - nip := net.ParseIP(ip) - na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) - test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380, - rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable} - return test - } - - tests := []ipTest{ - newIPTest("10.255.255.255", true, false, false, false, false, false, - false, false, false, false, false, false, false, false, true, false), - newIPTest("192.168.0.1", true, false, false, false, false, false, - false, false, false, false, false, false, false, false, true, false), - newIPTest("172.31.255.1", true, false, false, false, false, false, - false, false, false, false, false, false, false, false, true, false), - newIPTest("172.32.1.1", false, false, false, false, false, false, false, false, - false, false, false, false, false, false, true, true), - newIPTest("169.254.250.120", false, false, false, true, false, false, - false, false, false, false, false, false, false, false, true, false), - newIPTest("0.0.0.0", false, false, false, false, false, false, false, - false, false, false, false, false, false, true, false, false), - newIPTest("255.255.255.255", false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false), - newIPTest("127.0.0.1", false, false, false, false, false, false, - false, false, false, false, false, false, false, true, true, false), - newIPTest("2001::1", false, false, false, false, false, false, - true, false, false, false, false, false, false, false, true, true), - newIPTest("2001:10:abcd::1:1", false, false, false, false, false, false, - false, true, false, false, false, false, false, false, true, false), - newIPTest("fe80::1", false, false, false, false, false, false, - false, false, true, false, false, false, false, false, true, false), - newIPTest("fe80:1::1", false, false, false, false, false, false, - false, false, false, false, false, false, false, false, true, true), - newIPTest("64:ff9b::1", false, false, false, false, false, false, - false, false, false, false, true, false, false, false, true, true), - newIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false, - false, false, false, false, false, false, false, false, true, true), - newIPTest("::1", false, false, false, false, false, false, false, false, - false, false, false, false, false, true, true, false), - newIPTest("198.18.0.1", false, true, false, false, false, false, false, - false, false, false, false, false, false, false, true, false), - newIPTest("100.127.255.1", false, false, false, false, false, false, false, - false, false, false, false, false, true, false, true, false), - newIPTest("203.0.113.1", false, false, false, false, false, false, false, - false, false, false, false, false, false, false, true, false), - } - - t.Logf("Running %d tests", len(tests)) - for _, test := range tests { - if rv := addrmgr.IsRFC1918(&test.in); rv != test.rfc1918 { - t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918) - } - - if rv := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 { - t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849) - } - - if rv := addrmgr.IsRFC3927(&test.in); rv != test.rfc3927 { - t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927) - } - - if rv := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 { - t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964) - } - - if rv := addrmgr.IsRFC4193(&test.in); rv != test.rfc4193 { - t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193) - } - - if rv := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 { - t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380) - } - - if rv := addrmgr.IsRFC4843(&test.in); rv != test.rfc4843 { - t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843) - } - - if rv := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 { - t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862) - } - - if rv := addrmgr.IsRFC6052(&test.in); rv != test.rfc6052 { - t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052) - } - - if rv := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 { - t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145) - } - - if rv := addrmgr.IsLocal(&test.in); rv != test.local { - t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local) - } - - if rv := addrmgr.IsValid(&test.in); rv != test.valid { - t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid) - } - - if rv := addrmgr.IsRoutable(&test.in); rv != test.routable { - t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable) - } - } -} - -// TestGroupKey tests the GroupKey function to ensure it properly groups various -// IP addresses. -func TestGroupKey(t *testing.T) { - tests := []struct { - name string - ip string - expected string - }{ - // Local addresses. - {name: "ipv4 localhost", ip: "127.0.0.1", expected: "local"}, - {name: "ipv6 localhost", ip: "::1", expected: "local"}, - {name: "ipv4 zero", ip: "0.0.0.0", expected: "local"}, - {name: "ipv4 first octet zero", ip: "0.1.2.3", expected: "local"}, - - // Unroutable addresses. - {name: "ipv4 invalid bcast", ip: "255.255.255.255", expected: "unroutable"}, - {name: "ipv4 rfc1918 10/8", ip: "10.1.2.3", expected: "unroutable"}, - {name: "ipv4 rfc1918 172.16/12", ip: "172.16.1.2", expected: "unroutable"}, - {name: "ipv4 rfc1918 192.168/16", ip: "192.168.1.2", expected: "unroutable"}, - {name: "ipv6 rfc3849 2001:db8::/32", ip: "2001:db8::1234", expected: "unroutable"}, - {name: "ipv4 rfc3927 169.254/16", ip: "169.254.1.2", expected: "unroutable"}, - {name: "ipv6 rfc4193 fc00::/7", ip: "fc00::1234", expected: "unroutable"}, - {name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"}, - {name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"}, - - // IPv4 normal. - {name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"}, - {name: "ipv4 normal class b", ip: "173.1.2.3", expected: "173.1.0.0"}, - {name: "ipv4 normal class c", ip: "196.1.2.3", expected: "196.1.0.0"}, - - // IPv6/IPv4 translations. - {name: "ipv6 rfc3964 with ipv4 encap", ip: "2002:0c01:0203::", expected: "12.1.0.0"}, - {name: "ipv6 rfc4380 toredo ipv4", ip: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"}, - {name: "ipv6 rfc6052 well-known prefix with ipv4", ip: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, - {name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, - - // Tor. - {name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "tor:2"}, - {name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "tor:2"}, - {name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "tor:3"}, - - // IPv6 normal. - {name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"}, - {name: "ipv6 normal 2", ip: "2602:0100::1234", expected: "2602:100::"}, - {name: "ipv6 hurricane electric", ip: "2001:470:1f10:a1::2", expected: "2001:470:1000::"}, - {name: "ipv6 hurricane electric 2", ip: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"}, - } - - for i, test := range tests { - nip := net.ParseIP(test.ip) - na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) - if key := addrmgr.GroupKey(&na); key != test.expected { - t.Errorf("TestGroupKey #%d (%s): unexpected group key "+ - "- got '%s', want '%s'", i, test.name, - key, test.expected) - } - } -} +// Copyright (c) 2013-2014 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr_test + +import ( + "net" + "testing" + + "github.com/james-ray/hcd/addrmgr" + "github.com/james-ray/hcd/wire" +) + +// TestIPTypes ensures the various functions which determine the type of an IP +// address based on RFCs work as intended. +func TestIPTypes(t *testing.T) { + type ipTest struct { + in wire.NetAddress + rfc1918 bool + rfc2544 bool + rfc3849 bool + rfc3927 bool + rfc3964 bool + rfc4193 bool + rfc4380 bool + rfc4843 bool + rfc4862 bool + rfc5737 bool + rfc6052 bool + rfc6145 bool + rfc6598 bool + local bool + valid bool + routable bool + } + + newIPTest := func(ip string, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, + rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, + local, valid, routable bool) ipTest { + nip := net.ParseIP(ip) + na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) + test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380, + rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable} + return test + } + + tests := []ipTest{ + newIPTest("10.255.255.255", true, false, false, false, false, false, + false, false, false, false, false, false, false, false, true, false), + newIPTest("192.168.0.1", true, false, false, false, false, false, + false, false, false, false, false, false, false, false, true, false), + newIPTest("172.31.255.1", true, false, false, false, false, false, + false, false, false, false, false, false, false, false, true, false), + newIPTest("172.32.1.1", false, false, false, false, false, false, false, false, + false, false, false, false, false, false, true, true), + newIPTest("169.254.250.120", false, false, false, true, false, false, + false, false, false, false, false, false, false, false, true, false), + newIPTest("0.0.0.0", false, false, false, false, false, false, false, + false, false, false, false, false, false, true, false, false), + newIPTest("255.255.255.255", false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false), + newIPTest("127.0.0.1", false, false, false, false, false, false, + false, false, false, false, false, false, false, true, true, false), + newIPTest("2001::1", false, false, false, false, false, false, + true, false, false, false, false, false, false, false, true, true), + newIPTest("2001:10:abcd::1:1", false, false, false, false, false, false, + false, true, false, false, false, false, false, false, true, false), + newIPTest("fe80::1", false, false, false, false, false, false, + false, false, true, false, false, false, false, false, true, false), + newIPTest("fe80:1::1", false, false, false, false, false, false, + false, false, false, false, false, false, false, false, true, true), + newIPTest("64:ff9b::1", false, false, false, false, false, false, + false, false, false, false, true, false, false, false, true, true), + newIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false, + false, false, false, false, false, false, false, false, true, true), + newIPTest("::1", false, false, false, false, false, false, false, false, + false, false, false, false, false, true, true, false), + newIPTest("198.18.0.1", false, true, false, false, false, false, false, + false, false, false, false, false, false, false, true, false), + newIPTest("100.127.255.1", false, false, false, false, false, false, false, + false, false, false, false, false, true, false, true, false), + newIPTest("203.0.113.1", false, false, false, false, false, false, false, + false, false, false, false, false, false, false, true, false), + } + + t.Logf("Running %d tests", len(tests)) + for _, test := range tests { + if rv := addrmgr.IsRFC1918(&test.in); rv != test.rfc1918 { + t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918) + } + + if rv := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 { + t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849) + } + + if rv := addrmgr.IsRFC3927(&test.in); rv != test.rfc3927 { + t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927) + } + + if rv := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 { + t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964) + } + + if rv := addrmgr.IsRFC4193(&test.in); rv != test.rfc4193 { + t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193) + } + + if rv := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 { + t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380) + } + + if rv := addrmgr.IsRFC4843(&test.in); rv != test.rfc4843 { + t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843) + } + + if rv := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 { + t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862) + } + + if rv := addrmgr.IsRFC6052(&test.in); rv != test.rfc6052 { + t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052) + } + + if rv := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 { + t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145) + } + + if rv := addrmgr.IsLocal(&test.in); rv != test.local { + t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local) + } + + if rv := addrmgr.IsValid(&test.in); rv != test.valid { + t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid) + } + + if rv := addrmgr.IsRoutable(&test.in); rv != test.routable { + t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable) + } + } +} + +// TestGroupKey tests the GroupKey function to ensure it properly groups various +// IP addresses. +func TestGroupKey(t *testing.T) { + tests := []struct { + name string + ip string + expected string + }{ + // Local addresses. + {name: "ipv4 localhost", ip: "127.0.0.1", expected: "local"}, + {name: "ipv6 localhost", ip: "::1", expected: "local"}, + {name: "ipv4 zero", ip: "0.0.0.0", expected: "local"}, + {name: "ipv4 first octet zero", ip: "0.1.2.3", expected: "local"}, + + // Unroutable addresses. + {name: "ipv4 invalid bcast", ip: "255.255.255.255", expected: "unroutable"}, + {name: "ipv4 rfc1918 10/8", ip: "10.1.2.3", expected: "unroutable"}, + {name: "ipv4 rfc1918 172.16/12", ip: "172.16.1.2", expected: "unroutable"}, + {name: "ipv4 rfc1918 192.168/16", ip: "192.168.1.2", expected: "unroutable"}, + {name: "ipv6 rfc3849 2001:db8::/32", ip: "2001:db8::1234", expected: "unroutable"}, + {name: "ipv4 rfc3927 169.254/16", ip: "169.254.1.2", expected: "unroutable"}, + {name: "ipv6 rfc4193 fc00::/7", ip: "fc00::1234", expected: "unroutable"}, + {name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"}, + {name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"}, + + // IPv4 normal. + {name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"}, + {name: "ipv4 normal class b", ip: "173.1.2.3", expected: "173.1.0.0"}, + {name: "ipv4 normal class c", ip: "196.1.2.3", expected: "196.1.0.0"}, + + // IPv6/IPv4 translations. + {name: "ipv6 rfc3964 with ipv4 encap", ip: "2002:0c01:0203::", expected: "12.1.0.0"}, + {name: "ipv6 rfc4380 toredo ipv4", ip: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"}, + {name: "ipv6 rfc6052 well-known prefix with ipv4", ip: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, + {name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, + + // Tor. + {name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "tor:2"}, + {name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "tor:2"}, + {name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "tor:3"}, + + // IPv6 normal. + {name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"}, + {name: "ipv6 normal 2", ip: "2602:0100::1234", expected: "2602:100::"}, + {name: "ipv6 hurricane electric", ip: "2001:470:1f10:a1::2", expected: "2001:470:1000::"}, + {name: "ipv6 hurricane electric 2", ip: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"}, + } + + for i, test := range tests { + nip := net.ParseIP(test.ip) + na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) + if key := addrmgr.GroupKey(&na); key != test.expected { + t.Errorf("TestGroupKey #%d (%s): unexpected group key "+ + "- got '%s', want '%s'", i, test.name, + key, test.expected) + } + } +} diff --git a/addrmgr/test_coverage.txt b/addrmgr/test_coverage.txt index a982793c..a0eaf1ca 100644 --- a/addrmgr/test_coverage.txt +++ b/addrmgr/test_coverage.txt @@ -1,62 +1,62 @@ -github.com/HcashOrg/hcd/addrmgr/network.go GroupKey 100.00% (23/23) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.GetBestLocalAddress 100.00% (19/19) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.getNewBucket 100.00% (15/15) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.AddAddressByIP 100.00% (14/14) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.getTriedBucket 100.00% (14/14) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.AddLocalAddress 100.00% (11/11) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.pickTried 100.00% (8/8) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.reset 100.00% (6/6) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC1918 100.00% (4/4) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC5737 100.00% (4/4) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.AddAddresses 100.00% (4/4) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go New 100.00% (3/3) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.NeedMoreAddresses 100.00% (3/3) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.AddAddress 100.00% (3/3) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.NumAddresses 100.00% (3/3) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go NetAddressKey 100.00% (2/2) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC4862 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.numAddresses 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/log.go init 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/knownaddress.go KnownAddress.NetAddress 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/knownaddress.go KnownAddress.LastAttempt 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/log.go DisableLog 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go ipNet 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsIPv4 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsLocal 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsOnionCatTor 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC2544 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC3849 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC3927 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC3964 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC4193 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC4380 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC4843 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC6052 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC6145 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRFC6598 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsValid 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/network.go IsRoutable 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.find 100.00% (1/1) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.GetAddress 95.35% (41/43) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.Good 93.18% (41/44) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.Connected 90.00% (9/10) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.addressHandler 88.89% (8/9) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.AddressCache 87.50% (14/16) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.Attempt 85.71% (6/7) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.Start 83.33% (5/6) -github.com/HcashOrg/hcd/addrmgr/knownaddress.go KnownAddress.chance 76.92% (10/13) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.DeserializeNetAddress 71.43% (5/7) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.Stop 71.43% (5/7) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.updateAddress 53.33% (16/30) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go getReachabilityFrom 51.52% (17/33) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.savePeers 51.28% (20/39) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.expireNew 50.00% (11/22) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go ipString 50.00% (2/4) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.loadPeers 45.45% (5/11) -github.com/HcashOrg/hcd/addrmgr/knownaddress.go KnownAddress.isBad 36.36% (4/11) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.HostToNetAddress 26.67% (4/15) -github.com/HcashOrg/hcd/addrmgr/addrmanager.go AddrManager.deserializePeers 6.00% (3/50) -github.com/HcashOrg/hcd/addrmgr/log.go UseLogger 0.00% (0/1) -github.com/HcashOrg/hcd/addrmgr --------------------------------- 71.69% (385/537) +github.com/james-ray/hcd/addrmgr/network.go GroupKey 100.00% (23/23) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.GetBestLocalAddress 100.00% (19/19) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.getNewBucket 100.00% (15/15) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.AddAddressByIP 100.00% (14/14) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.getTriedBucket 100.00% (14/14) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.AddLocalAddress 100.00% (11/11) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.pickTried 100.00% (8/8) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.reset 100.00% (6/6) +github.com/james-ray/hcd/addrmgr/network.go IsRFC1918 100.00% (4/4) +github.com/james-ray/hcd/addrmgr/network.go IsRFC5737 100.00% (4/4) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.AddAddresses 100.00% (4/4) +github.com/james-ray/hcd/addrmgr/addrmanager.go New 100.00% (3/3) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.NeedMoreAddresses 100.00% (3/3) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.AddAddress 100.00% (3/3) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.NumAddresses 100.00% (3/3) +github.com/james-ray/hcd/addrmgr/addrmanager.go NetAddressKey 100.00% (2/2) +github.com/james-ray/hcd/addrmgr/network.go IsRFC4862 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.numAddresses 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/log.go init 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/knownaddress.go KnownAddress.NetAddress 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/knownaddress.go KnownAddress.LastAttempt 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/log.go DisableLog 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go ipNet 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsIPv4 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsLocal 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsOnionCatTor 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC2544 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC3849 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC3927 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC3964 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC4193 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC4380 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC4843 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC6052 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC6145 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRFC6598 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsValid 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/network.go IsRoutable 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.find 100.00% (1/1) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.GetAddress 95.35% (41/43) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.Good 93.18% (41/44) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.Connected 90.00% (9/10) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.addressHandler 88.89% (8/9) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.AddressCache 87.50% (14/16) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.Attempt 85.71% (6/7) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.Start 83.33% (5/6) +github.com/james-ray/hcd/addrmgr/knownaddress.go KnownAddress.chance 76.92% (10/13) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.DeserializeNetAddress 71.43% (5/7) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.Stop 71.43% (5/7) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.updateAddress 53.33% (16/30) +github.com/james-ray/hcd/addrmgr/addrmanager.go getReachabilityFrom 51.52% (17/33) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.savePeers 51.28% (20/39) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.expireNew 50.00% (11/22) +github.com/james-ray/hcd/addrmgr/addrmanager.go ipString 50.00% (2/4) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.loadPeers 45.45% (5/11) +github.com/james-ray/hcd/addrmgr/knownaddress.go KnownAddress.isBad 36.36% (4/11) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.HostToNetAddress 26.67% (4/15) +github.com/james-ray/hcd/addrmgr/addrmanager.go AddrManager.deserializePeers 6.00% (3/50) +github.com/james-ray/hcd/addrmgr/log.go UseLogger 0.00% (0/1) +github.com/james-ray/hcd/addrmgr --------------------------------- 71.69% (385/537) diff --git a/blockchain/README.md b/blockchain/README.md index 2f3e826e..03fba546 100644 --- a/blockchain/README.md +++ b/blockchain/README.md @@ -2,7 +2,7 @@ blockchain ========== [![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) -[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/HcashOrg/hcd/blockchain) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/james-ray/hcd/blockchain) Package blockchain implements HC block handling and chain selection rules. The test coverage is currently only around 60%, but will be increasing over @@ -20,7 +20,7 @@ block chain. ## Installation and Updating ```bash -$ go get -u github.com/HcashOrg/hcd/blockchain +$ go get -u github.com/james-ray/hcd/blockchain ``` ## Hcd Chain Processing Overview @@ -60,18 +60,18 @@ is by no means exhaustive: ## Examples -* [ProcessBlock Example](http://godoc.org/github.com/HcashOrg/hcd/blockchain#example-BlockChain-ProcessBlock) +* [ProcessBlock Example](http://godoc.org/github.com/james-ray/hcd/blockchain#example-BlockChain-ProcessBlock) Demonstrates how to create a new chain instance and use ProcessBlock to attempt to attempt add a block to the chain. This example intentionally attempts to insert a duplicate genesis block to illustrate how an invalid block is handled. -* [CompactToBig Example](http://godoc.org/github.com/HcashOrg/hcd/blockchain#example-CompactToBig) +* [CompactToBig Example](http://godoc.org/github.com/james-ray/hcd/blockchain#example-CompactToBig) Demonstrates how to convert the compact "bits" in a block header which represent the target difficulty to a big integer and display it using the typical hex notation. -* [BigToCompact Example](http://godoc.org/github.com/HcashOrg/hcd/blockchain#example-BigToCompact) +* [BigToCompact Example](http://godoc.org/github.com/james-ray/hcd/blockchain#example-BigToCompact) Demonstrates how to convert how to convert a target difficulty into the compact "bits" in a block header which represent that target difficulty. diff --git a/blockchain/accept.go b/blockchain/accept.go index e3f4eb69..d6aafc18 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -1,307 +1,307 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package blockchain - -import ( - "encoding/binary" - "fmt" - "math" - "time" - - "github.com/HcashOrg/hcd/blockchain/stake" - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/hcutil" - "github.com/HcashOrg/hcd/txscript" -) - -// checkCoinbaseUniqueHeight checks to ensure that for all blocks height > 1 -// that the coinbase contains the height encoding to make coinbase hash collisions -// impossible. -func checkCoinbaseUniqueHeight(blockHeight int64, block *hcutil.Block) error { - // Coinbase TxOut[0] is always tax, TxOut[1] is always - // height + extranonce, so at least two outputs must - // exist. - if len(block.MsgBlock().Transactions[0].TxOut) < 2 { - str := fmt.Sprintf("block %v is missing necessary coinbase "+ - "outputs", block.Hash()) - return ruleError(ErrFirstTxNotCoinbase, str) - } - - // The first 4 bytes of the NullData output must be the - // encoded height of the block, so that every coinbase - // created has a unique transaction hash. - nullData, err := txscript.GetNullDataContent( - block.MsgBlock().Transactions[0].TxOut[1].Version, - block.MsgBlock().Transactions[0].TxOut[1].PkScript) - if err != nil { - str := fmt.Sprintf("block %v txOut 1 has wrong pkScript "+ - "type", block.Hash()) - return ruleError(ErrFirstTxNotCoinbase, str) - } - - if len(nullData) < 4 { - str := fmt.Sprintf("block %v txOut 1 has too short nullData "+ - "push to contain height", block.Hash()) - return ruleError(ErrFirstTxNotCoinbase, str) - } - - // Check the height and ensure it is correct. - cbHeight := binary.LittleEndian.Uint32(nullData[0:4]) - if cbHeight != uint32(blockHeight) { - prevBlock := block.MsgBlock().Header.PrevBlock - str := fmt.Sprintf("block %v txOut 1 has wrong height in "+ - "coinbase; want %v, got %v; prevBlock %v, header height %v", - block.Hash(), blockHeight, cbHeight, prevBlock, - block.MsgBlock().Header.Height) - return ruleError(ErrCoinbaseHeight, str) - } - - return nil -} - -// IsFinalizedTransaction determines whether or not a transaction is finalized. -func IsFinalizedTransaction(tx *hcutil.Tx, blockHeight int64, blockTime time.Time) bool { - // Lock time of zero means the transaction is finalized. - msgTx := tx.MsgTx() - lockTime := msgTx.LockTime - if lockTime == 0 { - return true - } - - // The lock time field of a transaction is either a block height at - // which the transaction is finalized or a timestamp depending on if the - // value is before the txscript.LockTimeThreshold. When it is under the - // threshold it is a block height. - blockTimeOrHeight := int64(0) - if lockTime < txscript.LockTimeThreshold { - blockTimeOrHeight = blockHeight - } else { - blockTimeOrHeight = blockTime.Unix() - } - if int64(lockTime) < blockTimeOrHeight { - return true - } - - // At this point, the transaction's lock time hasn't occurred yet, but - // the transaction might still be finalized if the sequence number - // for all transaction inputs is maxed out. - for _, txIn := range msgTx.TxIn { - if txIn.Sequence != math.MaxUint32 { - return false - } - } - return true -} - -// checkBlockContext peforms several validation checks on the block which depend -// on its position within the block chain. -// -// The flags modify the behavior of this function as follows: -// - BFFastAdd: The transaction are not checked to see if they are finalized -// and the somewhat expensive duplication transaction check is not performed. -// -// The flags are also passed to checkBlockHeaderContext. See its documentation -// for how the flags modify its behavior. -func (b *BlockChain) checkBlockContext(block *hcutil.Block, prevNode *blockNode, flags BehaviorFlags) error { - // The genesis block is valid by definition. - if prevNode == nil { - return nil - } - - // Perform all block header related validation checks. - header := &block.MsgBlock().Header - err := b.checkBlockHeaderContext(header, prevNode, flags) - if err != nil { - return err - } - - fastAdd := flags&BFFastAdd == BFFastAdd - if !fastAdd { - // A block must not exceed the maximum allowed size as defined - // by the network parameters and the current status of any hard - // fork votes to change it when serialized. - maxBlockSize, err := b.maxBlockSize(prevNode) - if err != nil { - return err - } - serializedSize := int64(block.MsgBlock().Header.Size) - if serializedSize > maxBlockSize { - str := fmt.Sprintf("serialized block is too big - "+ - "got %d, max %d", serializedSize, - maxBlockSize) - return ruleError(ErrBlockTooBig, str) - } - - // Use the past median time of the block prior to - // the block being checked for all checks related to lock times - blockTime, err := b.calcPastMedianTime(prevNode) - if err != nil { - return err - } - - // The height of this block is one more than the referenced - // previous block. - blockHeight := prevNode.height + 1 - - // Ensure all transactions in the block are finalized. - for _, tx := range block.Transactions() { - if !IsFinalizedTransaction(tx, blockHeight, blockTime) { - str := fmt.Sprintf("block contains unfinalized regular "+ - "transaction %v", tx.Hash()) - return ruleError(ErrUnfinalizedTx, str) - } - } - for _, stx := range block.STransactions() { - if !IsFinalizedTransaction(stx, blockHeight, blockTime) { - str := fmt.Sprintf("block contains unfinalized stake "+ - "transaction %v", stx.Hash()) - return ruleError(ErrUnfinalizedTx, str) - } - } - - // Check that the node is at the correct height in the blockchain, - // as specified in the block header. - if blockHeight != int64(block.MsgBlock().Header.Height) { - errStr := fmt.Sprintf("Block header height invalid; expected %v"+ - " but %v was found", blockHeight, header.Height) - return ruleError(ErrBadBlockHeight, errStr) - } - - // Check that the coinbase contains at minimum the block - // height in output 1. - if blockHeight > 1 { - err := checkCoinbaseUniqueHeight(blockHeight, block) - if err != nil { - return err - } - } - } - - return nil -} - -// ticketsSpentInBlock fetches a list of tickets that were spent in the -// block. -func ticketsSpentInBlock(bl *hcutil.Block) []chainhash.Hash { - var tickets []chainhash.Hash - for _, stx := range bl.MsgBlock().STransactions { - if stake.DetermineTxType(stx) == stake.TxTypeSSGen { - tickets = append(tickets, stx.TxIn[1].PreviousOutPoint.Hash) - } - } - - return tickets -} - -// ticketsRevokedInBlock fetches a list of tickets that were revoked in the -// block. -func ticketsRevokedInBlock(bl *hcutil.Block) []chainhash.Hash { - var tickets []chainhash.Hash - for _, stx := range bl.MsgBlock().STransactions { - if stake.DetermineTxType(stx) == stake.TxTypeSSRtx { - tickets = append(tickets, stx.TxIn[0].PreviousOutPoint.Hash) - } - } - - return tickets -} - -// voteBitsInBlock returns a list of vote bits for the voters in this block. -func voteBitsInBlock(bl *hcutil.Block) []VoteVersionTuple { - var voteBits []VoteVersionTuple - for _, stx := range bl.MsgBlock().STransactions { - if is, _ := stake.IsSSGen(stx); !is { - continue - } - - voteBits = append(voteBits, VoteVersionTuple{ - Version: stake.SSGenVersion(stx), - Bits: stake.SSGenVoteBits(stx), - }) - } - - return voteBits -} - -// maybeAcceptBlock potentially accepts a block into the block chain and, if -// accepted, returns whether or not it is on the main chain. It performs -// several validation checks which depend on its position within the block chain -// before adding it. The block is expected to have already gone through -// ProcessBlock before calling this function with it. -// -// The flags modify the behavior of this function as follows: -// - BFDryRun: The memory chain index will not be pruned and no accept -// notification will be sent since the block is not being accepted. -// -// This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) maybeAcceptBlock(block *hcutil.Block, flags BehaviorFlags) (bool, error) { - dryRun := flags&BFDryRun == BFDryRun - - // Get a block node for the block previous to this one. Will be nil - // if this is the genesis block. - prevNode, err := b.getPrevNodeFromBlock(block) - if err != nil { - log.Debugf("getPrevNodeFromBlock: %v", err) - return false, err - } - - blockHeight := block.Height() - - // The block must pass all of the validation rules which depend on the - // position of the block within the block chain. - err = b.checkBlockContext(block, prevNode, flags) - if err != nil { - return false, err - } - - // Prune stake nodes which are no longer needed before creating a new - // node. - if !dryRun { - b.pruner.pruneChainIfNeeded() - } - - // Create a new block node for the block and add it to the in-memory - // block chain (could be either a side chain or the main chain). - blockHeader := &block.MsgBlock().Header - newNode := newBlockNode(blockHeader, ticketsSpentInBlock(block), - ticketsRevokedInBlock(block), voteBitsInBlock(block)) - if prevNode != nil { - newNode.parent = prevNode - newNode.height = blockHeight - newNode.workSum.Add(prevNode.workSum, newNode.workSum) - } - - // Fetching a stake node could enable a new DoS vector, so restrict - // this only to blocks that are recent in history. - if newNode.height < b.bestNode.height-minMemoryNodes { - newNode.stakeNode, err = b.fetchStakeNode(newNode) - if err != nil { - return false, err - } - newNode.stakeUndoData = newNode.stakeNode.UndoData() - } - - // Connect the passed block to the chain while respecting proper chain - // selection according to the chain with the most proof of work. This - // also handles validation of the transaction scripts. - isMainChain, err := b.connectBestChain(newNode, block, flags) - if err != nil { - return false, err - } - - // Notify the caller that the new block was accepted into the block - // chain. The caller would typically want to react by relaying the - // inventory to other peers. - if !dryRun { - b.chainLock.Unlock() - b.sendNotification(NTBlockAccepted, - &BlockAcceptedNtfnsData{isMainChain, block}) - b.chainLock.Lock() - } - - return isMainChain, nil -} +// Copyright (c) 2013-2016 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package blockchain + +import ( + "encoding/binary" + "fmt" + "math" + "time" + + "github.com/james-ray/hcd/blockchain/stake" + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/hcutil" + "github.com/james-ray/hcd/txscript" +) + +// checkCoinbaseUniqueHeight checks to ensure that for all blocks height > 1 +// that the coinbase contains the height encoding to make coinbase hash collisions +// impossible. +func checkCoinbaseUniqueHeight(blockHeight int64, block *hcutil.Block) error { + // Coinbase TxOut[0] is always tax, TxOut[1] is always + // height + extranonce, so at least two outputs must + // exist. + if len(block.MsgBlock().Transactions[0].TxOut) < 2 { + str := fmt.Sprintf("block %v is missing necessary coinbase "+ + "outputs", block.Hash()) + return ruleError(ErrFirstTxNotCoinbase, str) + } + + // The first 4 bytes of the NullData output must be the + // encoded height of the block, so that every coinbase + // created has a unique transaction hash. + nullData, err := txscript.GetNullDataContent( + block.MsgBlock().Transactions[0].TxOut[1].Version, + block.MsgBlock().Transactions[0].TxOut[1].PkScript) + if err != nil { + str := fmt.Sprintf("block %v txOut 1 has wrong pkScript "+ + "type", block.Hash()) + return ruleError(ErrFirstTxNotCoinbase, str) + } + + if len(nullData) < 4 { + str := fmt.Sprintf("block %v txOut 1 has too short nullData "+ + "push to contain height", block.Hash()) + return ruleError(ErrFirstTxNotCoinbase, str) + } + + // Check the height and ensure it is correct. + cbHeight := binary.LittleEndian.Uint32(nullData[0:4]) + if cbHeight != uint32(blockHeight) { + prevBlock := block.MsgBlock().Header.PrevBlock + str := fmt.Sprintf("block %v txOut 1 has wrong height in "+ + "coinbase; want %v, got %v; prevBlock %v, header height %v", + block.Hash(), blockHeight, cbHeight, prevBlock, + block.MsgBlock().Header.Height) + return ruleError(ErrCoinbaseHeight, str) + } + + return nil +} + +// IsFinalizedTransaction determines whether or not a transaction is finalized. +func IsFinalizedTransaction(tx *hcutil.Tx, blockHeight int64, blockTime time.Time) bool { + // Lock time of zero means the transaction is finalized. + msgTx := tx.MsgTx() + lockTime := msgTx.LockTime + if lockTime == 0 { + return true + } + + // The lock time field of a transaction is either a block height at + // which the transaction is finalized or a timestamp depending on if the + // value is before the txscript.LockTimeThreshold. When it is under the + // threshold it is a block height. + blockTimeOrHeight := int64(0) + if lockTime < txscript.LockTimeThreshold { + blockTimeOrHeight = blockHeight + } else { + blockTimeOrHeight = blockTime.Unix() + } + if int64(lockTime) < blockTimeOrHeight { + return true + } + + // At this point, the transaction's lock time hasn't occurred yet, but + // the transaction might still be finalized if the sequence number + // for all transaction inputs is maxed out. + for _, txIn := range msgTx.TxIn { + if txIn.Sequence != math.MaxUint32 { + return false + } + } + return true +} + +// checkBlockContext peforms several validation checks on the block which depend +// on its position within the block chain. +// +// The flags modify the behavior of this function as follows: +// - BFFastAdd: The transaction are not checked to see if they are finalized +// and the somewhat expensive duplication transaction check is not performed. +// +// The flags are also passed to checkBlockHeaderContext. See its documentation +// for how the flags modify its behavior. +func (b *BlockChain) checkBlockContext(block *hcutil.Block, prevNode *blockNode, flags BehaviorFlags) error { + // The genesis block is valid by definition. + if prevNode == nil { + return nil + } + + // Perform all block header related validation checks. + header := &block.MsgBlock().Header + err := b.checkBlockHeaderContext(header, prevNode, flags) + if err != nil { + return err + } + + fastAdd := flags&BFFastAdd == BFFastAdd + if !fastAdd { + // A block must not exceed the maximum allowed size as defined + // by the network parameters and the current status of any hard + // fork votes to change it when serialized. + maxBlockSize, err := b.maxBlockSize(prevNode) + if err != nil { + return err + } + serializedSize := int64(block.MsgBlock().Header.Size) + if serializedSize > maxBlockSize { + str := fmt.Sprintf("serialized block is too big - "+ + "got %d, max %d", serializedSize, + maxBlockSize) + return ruleError(ErrBlockTooBig, str) + } + + // Use the past median time of the block prior to + // the block being checked for all checks related to lock times + blockTime, err := b.calcPastMedianTime(prevNode) + if err != nil { + return err + } + + // The height of this block is one more than the referenced + // previous block. + blockHeight := prevNode.height + 1 + + // Ensure all transactions in the block are finalized. + for _, tx := range block.Transactions() { + if !IsFinalizedTransaction(tx, blockHeight, blockTime) { + str := fmt.Sprintf("block contains unfinalized regular "+ + "transaction %v", tx.Hash()) + return ruleError(ErrUnfinalizedTx, str) + } + } + for _, stx := range block.STransactions() { + if !IsFinalizedTransaction(stx, blockHeight, blockTime) { + str := fmt.Sprintf("block contains unfinalized stake "+ + "transaction %v", stx.Hash()) + return ruleError(ErrUnfinalizedTx, str) + } + } + + // Check that the node is at the correct height in the blockchain, + // as specified in the block header. + if blockHeight != int64(block.MsgBlock().Header.Height) { + errStr := fmt.Sprintf("Block header height invalid; expected %v"+ + " but %v was found", blockHeight, header.Height) + return ruleError(ErrBadBlockHeight, errStr) + } + + // Check that the coinbase contains at minimum the block + // height in output 1. + if blockHeight > 1 { + err := checkCoinbaseUniqueHeight(blockHeight, block) + if err != nil { + return err + } + } + } + + return nil +} + +// ticketsSpentInBlock fetches a list of tickets that were spent in the +// block. +func ticketsSpentInBlock(bl *hcutil.Block) []chainhash.Hash { + var tickets []chainhash.Hash + for _, stx := range bl.MsgBlock().STransactions { + if stake.DetermineTxType(stx) == stake.TxTypeSSGen { + tickets = append(tickets, stx.TxIn[1].PreviousOutPoint.Hash) + } + } + + return tickets +} + +// ticketsRevokedInBlock fetches a list of tickets that were revoked in the +// block. +func ticketsRevokedInBlock(bl *hcutil.Block) []chainhash.Hash { + var tickets []chainhash.Hash + for _, stx := range bl.MsgBlock().STransactions { + if stake.DetermineTxType(stx) == stake.TxTypeSSRtx { + tickets = append(tickets, stx.TxIn[0].PreviousOutPoint.Hash) + } + } + + return tickets +} + +// voteBitsInBlock returns a list of vote bits for the voters in this block. +func voteBitsInBlock(bl *hcutil.Block) []VoteVersionTuple { + var voteBits []VoteVersionTuple + for _, stx := range bl.MsgBlock().STransactions { + if is, _ := stake.IsSSGen(stx); !is { + continue + } + + voteBits = append(voteBits, VoteVersionTuple{ + Version: stake.SSGenVersion(stx), + Bits: stake.SSGenVoteBits(stx), + }) + } + + return voteBits +} + +// maybeAcceptBlock potentially accepts a block into the block chain and, if +// accepted, returns whether or not it is on the main chain. It performs +// several validation checks which depend on its position within the block chain +// before adding it. The block is expected to have already gone through +// ProcessBlock before calling this function with it. +// +// The flags modify the behavior of this function as follows: +// - BFDryRun: The memory chain index will not be pruned and no accept +// notification will be sent since the block is not being accepted. +// +// This function MUST be called with the chain state lock held (for writes). +func (b *BlockChain) maybeAcceptBlock(block *hcutil.Block, flags BehaviorFlags) (bool, error) { + dryRun := flags&BFDryRun == BFDryRun + + // Get a block node for the block previous to this one. Will be nil + // if this is the genesis block. + prevNode, err := b.getPrevNodeFromBlock(block) + if err != nil { + log.Debugf("getPrevNodeFromBlock: %v", err) + return false, err + } + + blockHeight := block.Height() + + // The block must pass all of the validation rules which depend on the + // position of the block within the block chain. + err = b.checkBlockContext(block, prevNode, flags) + if err != nil { + return false, err + } + + // Prune stake nodes which are no longer needed before creating a new + // node. + if !dryRun { + b.pruner.pruneChainIfNeeded() + } + + // Create a new block node for the block and add it to the in-memory + // block chain (could be either a side chain or the main chain). + blockHeader := &block.MsgBlock().Header + newNode := newBlockNode(blockHeader, ticketsSpentInBlock(block), + ticketsRevokedInBlock(block), voteBitsInBlock(block)) + if prevNode != nil { + newNode.parent = prevNode + newNode.height = blockHeight + newNode.workSum.Add(prevNode.workSum, newNode.workSum) + } + + // Fetching a stake node could enable a new DoS vector, so restrict + // this only to blocks that are recent in history. + if newNode.height < b.bestNode.height-minMemoryNodes { + newNode.stakeNode, err = b.fetchStakeNode(newNode) + if err != nil { + return false, err + } + newNode.stakeUndoData = newNode.stakeNode.UndoData() + } + + // Connect the passed block to the chain while respecting proper chain + // selection according to the chain with the most proof of work. This + // also handles validation of the transaction scripts. + isMainChain, err := b.connectBestChain(newNode, block, flags) + if err != nil { + return false, err + } + + // Notify the caller that the new block was accepted into the block + // chain. The caller would typically want to react by relaying the + // inventory to other peers. + if !dryRun { + b.chainLock.Unlock() + b.sendNotification(NTBlockAccepted, + &BlockAcceptedNtfnsData{isMainChain, block}) + b.chainLock.Lock() + } + + return isMainChain, nil +} diff --git a/blockchain/blocklocator.go b/blockchain/blocklocator.go index 3f3ecad0..abf71873 100644 --- a/blockchain/blocklocator.go +++ b/blockchain/blocklocator.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2015-2017 The Decred developers // Copyright (c) 2018-2020 The Hc developers // Use of this source code is governed by an ISC @@ -8,9 +8,9 @@ package blockchain import ( - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/database" - "github.com/HcashOrg/hcd/wire" + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/database" + "github.com/james-ray/hcd/wire" ) // BlockLocator is used to help locate a specific block. The algorithm for @@ -26,8 +26,9 @@ import ( // numbers are derived. // For example, assume you have a block chain with a side chain as depicted // below: -// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 -// \-> 16a -> 17a +// +// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 +// \-> 16a -> 17a // // The block locator for block 17a would be the hashes of blocks: // [17a 16a 15 14 13 12 11 10 9 8 6 2 genesis] @@ -39,10 +40,10 @@ type BlockLocator []*chainhash.Hash // In addition to the general algorithm referenced above, there are a couple of // special cases which are handled: // -// - If the genesis hash is passed, there are no previous hashes to add and -// therefore the block locator will only consist of the genesis hash -// - If the passed hash is not currently known, the block locator will be for -// the latest known tip of the main (best) chain +// - If the genesis hash is passed, there are no previous hashes to add and +// therefore the block locator will only consist of the genesis hash +// - If the passed hash is not currently known, the block locator will be for +// the latest known tip of the main (best) chain // // This function MUST be called with the chain state lock held (for reads). func (b *BlockChain) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { @@ -148,6 +149,7 @@ func (b *BlockChain) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { locator = append(locator, b.chainParams.GenesisHash) return locator } + // fastLog2Floor calculates and returns floor(log2(x)) in a constant 5 steps. //func fastLog2Floor(n uint32) uint8 { // rv := uint8(0) @@ -168,10 +170,10 @@ func (b *BlockChain) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { // In addition to the general algorithm referenced above, there are a couple of // special cases which are handled: // -// - If the genesis hash is passed, there are no previous hashes to add and -// therefore the block locator will only consist of the genesis hash -// - If the passed hash is not currently known, the block locator will only -// consist of the passed hash +// - If the genesis hash is passed, there are no previous hashes to add and +// therefore the block locator will only consist of the genesis hash +// - If the passed hash is not currently known, the block locator will only +// consist of the passed hash // // This function is safe for concurrent access. func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator { diff --git a/blockchain/chain.go b/blockchain/chain.go index 93c41859..32ac9e9a 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -14,13 +14,13 @@ import ( "sync" "time" - "github.com/HcashOrg/hcd/blockchain/stake" - "github.com/HcashOrg/hcd/chaincfg" - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/database" - "github.com/HcashOrg/hcd/hcutil" - "github.com/HcashOrg/hcd/txscript" - "github.com/HcashOrg/hcd/wire" + "github.com/james-ray/hcd/blockchain/stake" + "github.com/james-ray/hcd/chaincfg" + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/database" + "github.com/james-ray/hcd/hcutil" + "github.com/james-ray/hcd/txscript" + "github.com/james-ray/hcd/wire" ) const ( @@ -982,19 +982,19 @@ func (b *BlockChain) pruneStakeNodes() { // just before each new node is created. However, that might be tuned // later to only prune at intervals, so the code needs to account for // the possibility of multiple nodes. -// deleteNodes := list.New() + // deleteNodes := list.New() var deleteNodes []*blockNode - + for node := pruneToNode.parent; node != nil; node = node.parent { -// deleteNodes.PushFront(node) + // deleteNodes.PushFront(node) deleteNodes = append(deleteNodes, node) } // Loop through each node to prune, unlink its children, remove it from // the dependency index, and remove it from the node index. -// for e := deleteNodes.Front(); e != nil; e = e.Next() { + // for e := deleteNodes.Front(); e != nil; e = e.Next() { for i := len(deleteNodes) - 1; i >= 0; i-- { -// node := e.Value.(*blockNode) + // node := e.Value.(*blockNode) node := deleteNodes[i] // Do not attempt to prune if the node should already have been pruned, // for example if you're adding an old side chain block. @@ -1591,8 +1591,8 @@ func countNumberOfTransactions(block, parent *hcutil.Block) uint64 { // (think pushing them onto the end of the chain). // // The flags modify the behavior of this function as follows: -// - BFDryRun: Only the checks which ensure the reorganize can be completed -// successfully are performed. The chain is not reorganized. +// - BFDryRun: Only the checks which ensure the reorganize can be completed +// successfully are performed. The chain is not reorganized. // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, @@ -1928,11 +1928,11 @@ func (b *BlockChain) ForceHeadReorganization(formerBest chainhash.Hash, newBest // a reorganization to become the main chain). // // The flags modify the behavior of this function as follows: -// - BFFastAdd: Avoids several expensive transaction validation operations. -// This is useful when using checkpoints. -// - BFDryRun: Prevents the block from being connected and avoids modifying the -// state of the memory chain index. Also, any log messages related to -// modifying the state are avoided. +// - BFFastAdd: Avoids several expensive transaction validation operations. +// This is useful when using checkpoints. +// - BFDryRun: Prevents the block from being connected and avoids modifying the +// state of the memory chain index. Also, any log messages related to +// modifying the state are avoided. // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) connectBestChain(node *blockNode, block *hcutil.Block, flags BehaviorFlags) (bool, error) { @@ -2113,8 +2113,8 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *hcutil.Block, flag // isCurrent returns whether or not the chain believes it is current. Several // factors are used to guess, but the key factors that allow the chain to // believe it is current are: -// - Latest block height is after the latest checkpoint (if enabled) -// - Latest block has a timestamp newer than 24 hours ago +// - Latest block height is after the latest checkpoint (if enabled) +// - Latest block has a timestamp newer than 24 hours ago // // This function MUST be called with the chain state lock held (for reads). func (b *BlockChain) isCurrent() bool { @@ -2137,8 +2137,8 @@ func (b *BlockChain) isCurrent() bool { // IsCurrent returns whether or not the chain believes it is current. Several // factors are used to guess, but the key factors that allow the chain to // believe it is current are: -// - Latest block height is after the latest checkpoint (if enabled) -// - Latest block has a timestamp newer than 24 hours ago +// - Latest block height is after the latest checkpoint (if enabled) +// - Latest block has a timestamp newer than 24 hours ago // // This function is safe for concurrent access. func (b *BlockChain) IsCurrent() bool { diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 4dbcc570..3dd3610e 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -1,131 +1,132 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package blockchain_test - -import ( - "bytes" - "compress/bzip2" - "encoding/gob" - "os" - "path/filepath" - "testing" - - "github.com/HcashOrg/hcd/blockchain" - "github.com/HcashOrg/hcd/chaincfg" - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/hcutil" -) - -// cloneParams returns a deep copy of the provided parameters so the caller is -// free to modify them without worrying about interfering with other tests. -func cloneParams(params *chaincfg.Params) *chaincfg.Params { - // Encode via gob. - buf := new(bytes.Buffer) - enc := gob.NewEncoder(buf) - enc.Encode(params) - - // Decode via gob to make a deep copy. - var paramsCopy chaincfg.Params - dec := gob.NewDecoder(buf) - dec.Decode(¶msCopy) - return ¶msCopy -} - -// mustParseHash converts the passed big-endian hex string into a -// chainhash.Hash and will panic if there is an error. It only differs from the -// one available in chainhash in that it will panic so errors in the source code -// be detected. It will only (and must only) be called with hard-coded, and -// therefore known good, hashes. -func mustParseHash(s string) *chainhash.Hash { - hash, err := chainhash.NewHashFromStr(s) - if err != nil { - panic("invalid hash in source file: " + s) - } - return hash -} -// rejected expects the block to be rejected with the provided error -// TestBlockchainFunction tests the various blockchain API to ensure proper -// functionality. -func TestBlockchainFunctions(t *testing.T) { - // Update simnet parameters to reflect what is expected by the legacy - // data. - params := cloneParams(&chaincfg.SimNetParams) - params.GenesisBlock.Header.MerkleRoot = *mustParseHash("a216ea043f0d481a072424af646787794c32bcefd3ed181a090319bbf8a37105") - genesisHash := params.GenesisBlock.BlockHash() - params.GenesisHash = &genesisHash - - // Create a new database and chain instance to run tests against. - chain, teardownFunc, err := chainSetup("validateunittests", params) - if err != nil { - t.Errorf("Failed to setup chain instance: %v", err) - return - } - defer teardownFunc() - - // Load up the rest of the blocks up to HEAD~1. - filename := filepath.Join("testdata/", "blocks0to168.bz2") - fi, err := os.Open(filename) - if err != nil { - t.Errorf("Unable to open %s: %v", filename, err) - } - bcStream := bzip2.NewReader(fi) - defer fi.Close() - - // Create a buffer of the read file. - bcBuf := new(bytes.Buffer) - bcBuf.ReadFrom(bcStream) - - // Create decoder from the buffer and a map to store the data. - bcDecoder := gob.NewDecoder(bcBuf) - blockChain := make(map[int64][]byte) - - // Decode the blockchain into the map. - if err := bcDecoder.Decode(&blockChain); err != nil { - t.Errorf("error decoding test blockchain: %v", err.Error()) - } - // Insert blocks 1 to 180 and perform various tests. - for i := 1; i <= 180; i++ { - bl, err := hcutil.NewBlockFromBytes(blockChain[int64(i)]) - if err != nil { - t.Errorf("NewBlockFromBytes error: %v", err.Error()) - } - - _, _, err = chain.ProcessBlock(bl, blockchain.BFNone) - if err != nil { - t.Fatalf("ProcessBlock error at height %v: %v", i, err.Error()) - } - } - - val, err := chain.TicketPoolValue() - if err != nil { - t.Errorf("Failed to get ticket pool value: %v", err) - } - expectedVal := hcutil.Amount(3495091704) - if val != expectedVal { - t.Errorf("Failed to get correct result for ticket pool value; "+ - "want %v, got %v", expectedVal, val) - } - - a, _ := hcutil.DecodeAddress("SsbKpMkPnadDcZFFZqRPY8nvdFagrktKuzB") - hs, err := chain.TicketsWithAddress(a) - if err != nil { - t.Errorf("Failed to do TicketsWithAddress: %v", err) - } - expectedLen := 223 - if len(hs) != expectedLen { - t.Errorf("Failed to get correct number of tickets for "+ - "TicketsWithAddress; want %v, got %v", expectedLen, len(hs)) - } - - totalSubsidy := chain.TotalSubsidy() - expectedSubsidy := int64(35783267326630) - if expectedSubsidy != totalSubsidy { - t.Errorf("Failed to get correct total subsidy for "+ - "TotalSubsidy; want %v, got %v", expectedSubsidy, - totalSubsidy) - } -} +// Copyright (c) 2013-2016 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package blockchain_test + +import ( + "bytes" + "compress/bzip2" + "encoding/gob" + "os" + "path/filepath" + "testing" + + "github.com/james-ray/hcd/blockchain" + "github.com/james-ray/hcd/chaincfg" + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/hcutil" +) + +// cloneParams returns a deep copy of the provided parameters so the caller is +// free to modify them without worrying about interfering with other tests. +func cloneParams(params *chaincfg.Params) *chaincfg.Params { + // Encode via gob. + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + enc.Encode(params) + + // Decode via gob to make a deep copy. + var paramsCopy chaincfg.Params + dec := gob.NewDecoder(buf) + dec.Decode(¶msCopy) + return ¶msCopy +} + +// mustParseHash converts the passed big-endian hex string into a +// chainhash.Hash and will panic if there is an error. It only differs from the +// one available in chainhash in that it will panic so errors in the source code +// be detected. It will only (and must only) be called with hard-coded, and +// therefore known good, hashes. +func mustParseHash(s string) *chainhash.Hash { + hash, err := chainhash.NewHashFromStr(s) + if err != nil { + panic("invalid hash in source file: " + s) + } + return hash +} + +// rejected expects the block to be rejected with the provided error +// TestBlockchainFunction tests the various blockchain API to ensure proper +// functionality. +func TestBlockchainFunctions(t *testing.T) { + // Update simnet parameters to reflect what is expected by the legacy + // data. + params := cloneParams(&chaincfg.SimNetParams) + params.GenesisBlock.Header.MerkleRoot = *mustParseHash("a216ea043f0d481a072424af646787794c32bcefd3ed181a090319bbf8a37105") + genesisHash := params.GenesisBlock.BlockHash() + params.GenesisHash = &genesisHash + + // Create a new database and chain instance to run tests against. + chain, teardownFunc, err := chainSetup("validateunittests", params) + if err != nil { + t.Errorf("Failed to setup chain instance: %v", err) + return + } + defer teardownFunc() + + // Load up the rest of the blocks up to HEAD~1. + filename := filepath.Join("testdata/", "blocks0to168.bz2") + fi, err := os.Open(filename) + if err != nil { + t.Errorf("Unable to open %s: %v", filename, err) + } + bcStream := bzip2.NewReader(fi) + defer fi.Close() + + // Create a buffer of the read file. + bcBuf := new(bytes.Buffer) + bcBuf.ReadFrom(bcStream) + + // Create decoder from the buffer and a map to store the data. + bcDecoder := gob.NewDecoder(bcBuf) + blockChain := make(map[int64][]byte) + + // Decode the blockchain into the map. + if err := bcDecoder.Decode(&blockChain); err != nil { + t.Errorf("error decoding test blockchain: %v", err.Error()) + } + // Insert blocks 1 to 180 and perform various tests. + for i := 1; i <= 180; i++ { + bl, err := hcutil.NewBlockFromBytes(blockChain[int64(i)]) + if err != nil { + t.Errorf("NewBlockFromBytes error: %v", err.Error()) + } + + _, _, err = chain.ProcessBlock(bl, blockchain.BFNone) + if err != nil { + t.Fatalf("ProcessBlock error at height %v: %v", i, err.Error()) + } + } + + val, err := chain.TicketPoolValue() + if err != nil { + t.Errorf("Failed to get ticket pool value: %v", err) + } + expectedVal := hcutil.Amount(3495091704) + if val != expectedVal { + t.Errorf("Failed to get correct result for ticket pool value; "+ + "want %v, got %v", expectedVal, val) + } + + a, _ := hcutil.DecodeAddress("SsbKpMkPnadDcZFFZqRPY8nvdFagrktKuzB") + hs, err := chain.TicketsWithAddress(a) + if err != nil { + t.Errorf("Failed to do TicketsWithAddress: %v", err) + } + expectedLen := 223 + if len(hs) != expectedLen { + t.Errorf("Failed to get correct number of tickets for "+ + "TicketsWithAddress; want %v, got %v", expectedLen, len(hs)) + } + + totalSubsidy := chain.TotalSubsidy() + expectedSubsidy := int64(35783267326630) + if expectedSubsidy != totalSubsidy { + t.Errorf("Failed to get correct total subsidy for "+ + "TotalSubsidy; want %v, got %v", expectedSubsidy, + totalSubsidy) + } +} diff --git a/blockchain/chaingen/README.md b/blockchain/chaingen/README.md index e985fdf4..149724fa 100644 --- a/blockchain/chaingen/README.md +++ b/blockchain/chaingen/README.md @@ -3,7 +3,7 @@ chaingen [![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)] -(http://godoc.org/github.com/HcashOrg/hcd/blockchain/chaingen) +(http://godoc.org/github.com/james-ray/hcd/blockchain/chaingen) Package chaingen provides facilities for generating a full chain of blocks. @@ -26,7 +26,7 @@ functions. ## Examples * [Basic Usage Example] - (http://godoc.org/github.com/HcashOrg/hcd/blockchain/chaingen#example-package--BasicUsage) + (http://godoc.org/github.com/james-ray/hcd/blockchain/chaingen#example-package--BasicUsage) Demonstrates creating a new generator instance and using it to generate the required premine block and enough blocks to have mature coinbase outputs to work with along with asserting the generator state along the way. @@ -34,7 +34,7 @@ functions. ## Installation ```bash -$ go get -u github.com/HcashOrg/hcd/blockchain/chaingen +$ go get -u github.com/james-ray/hcd/blockchain/chaingen ``` ## License diff --git a/blockchain/chaingen/example_test.go b/blockchain/chaingen/example_test.go index 9dafcc73..d2a8bca7 100644 --- a/blockchain/chaingen/example_test.go +++ b/blockchain/chaingen/example_test.go @@ -1,70 +1,70 @@ -// Copyright (c) 2017 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package chaingen_test - -import ( - "fmt" - - "github.com/HcashOrg/hcd/blockchain/chaingen" - "github.com/HcashOrg/hcd/chaincfg" -) - -// This example demonstrates creating a new generator instance and using it to -// generate the required premine block and enough blocks to have mature coinbase -// outputs to work with along with asserting the generator state along the way. -func Example_basicUsage() { - params := &chaincfg.SimNetParams - g, err := chaingen.MakeGenerator(params) - if err != nil { - fmt.Println(err) - return - } - - // Shorter versions of useful params for convenience. - coinbaseMaturity := params.CoinbaseMaturity - - // --------------------------------------------------------------------- - // Premine. - // --------------------------------------------------------------------- - - // Add the required premine block. - // - // genesis -> bp - g.CreatePremineBlock("bp", 0) - g.AssertTipHeight(1) - fmt.Println(g.TipName()) - - // --------------------------------------------------------------------- - // Generate enough blocks to have mature coinbase outputs to work with. - // - // genesis -> bp -> bm0 -> bm1 -> ... -> bm# - // --------------------------------------------------------------------- - - for i := uint16(0); i < coinbaseMaturity; i++ { - blockName := fmt.Sprintf("bm%d", i) - g.NextBlock(blockName, nil, nil) - g.SaveTipCoinbaseOuts() - fmt.Println(g.TipName()) - } - g.AssertTipHeight(uint32(coinbaseMaturity) + 1) - // below is the output - // bp - // bm0 - // bm1 - // bm2 - // bm3 - // bm4 - // bm5 - // bm6 - // bm7 - // bm8 - // bm9 - // bm10 - // bm11 - // bm12 - // bm13 - // bm14 - // bm15 -} +// Copyright (c) 2017 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package chaingen_test + +import ( + "fmt" + + "github.com/james-ray/hcd/blockchain/chaingen" + "github.com/james-ray/hcd/chaincfg" +) + +// This example demonstrates creating a new generator instance and using it to +// generate the required premine block and enough blocks to have mature coinbase +// outputs to work with along with asserting the generator state along the way. +func Example_basicUsage() { + params := &chaincfg.SimNetParams + g, err := chaingen.MakeGenerator(params) + if err != nil { + fmt.Println(err) + return + } + + // Shorter versions of useful params for convenience. + coinbaseMaturity := params.CoinbaseMaturity + + // --------------------------------------------------------------------- + // Premine. + // --------------------------------------------------------------------- + + // Add the required premine block. + // + // genesis -> bp + g.CreatePremineBlock("bp", 0) + g.AssertTipHeight(1) + fmt.Println(g.TipName()) + + // --------------------------------------------------------------------- + // Generate enough blocks to have mature coinbase outputs to work with. + // + // genesis -> bp -> bm0 -> bm1 -> ... -> bm# + // --------------------------------------------------------------------- + + for i := uint16(0); i < coinbaseMaturity; i++ { + blockName := fmt.Sprintf("bm%d", i) + g.NextBlock(blockName, nil, nil) + g.SaveTipCoinbaseOuts() + fmt.Println(g.TipName()) + } + g.AssertTipHeight(uint32(coinbaseMaturity) + 1) + // below is the output + // bp + // bm0 + // bm1 + // bm2 + // bm3 + // bm4 + // bm5 + // bm6 + // bm7 + // bm8 + // bm9 + // bm10 + // bm11 + // bm12 + // bm13 + // bm14 + // bm15 +} diff --git a/blockchain/chaingen/generator.go b/blockchain/chaingen/generator.go index 8d290634..a97e9349 100644 --- a/blockchain/chaingen/generator.go +++ b/blockchain/chaingen/generator.go @@ -1,2114 +1,2118 @@ -// Copyright (c) 2016-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package chaingen - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "math/big" - "runtime" - "sort" - "time" - - "github.com/HcashOrg/hcd/blockchain" - "github.com/HcashOrg/hcd/chaincfg" - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/txscript" - "github.com/HcashOrg/hcd/wire" - "github.com/HcashOrg/hcd/hcutil" -) - -var ( - // hash256prngSeedConst is a constant derived from the hex - // representation of pi and is used in conjuction with a caller-provided - // seed when initializing the deterministic lottery prng. - hash256prngSeedConst = []byte{0x24, 0x3f, 0x6a, 0x88, 0x85, 0xa3, 0x08, - 0xd3} - - // opTrueScript is a simple public key script that contains the OP_TRUE - // opcode. It is defined here to reduce garbage creation. - opTrueScript = []byte{txscript.OP_TRUE} - - // opTrueRedeemScript is the signature script that can be used to redeem - // a p2sh output to the opTrueScript. It is defined here to reduce - // garbage creation. - opTrueRedeemScript = []byte{txscript.OP_DATA_1, txscript.OP_TRUE} - - // coinbaseSigScript is the signature script used by the tests when - // creating standard coinbase transactions. It is defined here to - // reduce garbage creation. - coinbaseSigScript = []byte{txscript.OP_0, txscript.OP_0} -) - -const ( - // voteBitYes is the specific bit that is set in the vote bits to - // indicate that the previous block is valid. - voteBitYes = 0x01 -) - -// SpendableOut represents a transaction output that is spendable along with -// additional metadata such as the block its in and how much it pays. -type SpendableOut struct { - prevOut wire.OutPoint - blockHeight uint32 - blockIndex uint32 - amount hcutil.Amount -} - -// PrevOut returns the outpoint associated with the spendable output. -func (s *SpendableOut) PrevOut() wire.OutPoint { - return s.prevOut -} - -// BlockHeight returns the block height of the block the spendable output is in. -func (s *SpendableOut) BlockHeight() uint32 { - return s.blockHeight -} - -// BlockIndex returns the offset into the block the spendable output is in. -func (s *SpendableOut) BlockIndex() uint32 { - return s.blockIndex -} - -// Amount returns the amount associated with the spendable output. -func (s *SpendableOut) Amount() hcutil.Amount { - return s.amount -} - -// MakeSpendableOutForTx returns a spendable output for the given transaction -// block height, transaction index within the block, and transaction output -// index within the transaction. -func MakeSpendableOutForTx(tx *wire.MsgTx, blockHeight, txIndex, txOutIndex uint32) SpendableOut { - return SpendableOut{ - prevOut: wire.OutPoint{ - Hash: *tx.CachedTxHash(), - Index: txOutIndex, - Tree: wire.TxTreeRegular, - }, - blockHeight: blockHeight, - blockIndex: txIndex, - amount: hcutil.Amount(tx.TxOut[txOutIndex].Value), - } -} - -// MakeSpendableOut returns a spendable output for the given block, transaction -// index within the block, and transaction output index within the transaction. -func MakeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) SpendableOut { - tx := block.Transactions[txIndex] - return MakeSpendableOutForTx(tx, block.Header.Height, txIndex, txOutIndex) -} - -// stakeTicket represents a transaction that is an sstx along with the height of -// the block it was mined in and the its index within that block. -type stakeTicket struct { - tx *wire.MsgTx - blockHeight uint32 - blockIndex uint32 -} - -// stakeTicketSorter implements sort.Interface to allow a slice of stake tickets -// to be sorted. -type stakeTicketSorter []*stakeTicket - -// Len returns the number of stake tickets in the slice. It is part of the -// sort.Interface implementation. -func (t stakeTicketSorter) Len() int { return len(t) } - -// Swap swaps the stake tickets at the passed indices. It is part of the -// sort.Interface implementation. -func (t stakeTicketSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] } - -// Less returns whether the stake ticket with index i should sort before the -// stake ticket with index j. It is part of the sort.Interface implementation. -func (t stakeTicketSorter) Less(i, j int) bool { - iHash := t[i].tx.CachedTxHash()[:] - jHash := t[j].tx.CachedTxHash()[:] - return bytes.Compare(iHash, jHash) < 0 -} - -// Generator houses state used to ease the process of generating test blocks -// that build from one another along with housing other useful things such as -// available spendable outputs and generic payment scripts used throughout the -// tests. -type Generator struct { - params *chaincfg.Params - tip *wire.MsgBlock - tipName string - blocks map[chainhash.Hash]*wire.MsgBlock - blocksByName map[string]*wire.MsgBlock - p2shOpTrueAddr hcutil.Address - p2shOpTrueScript []byte - - // Used for tracking spendable coinbase outputs. - spendableOuts [][]SpendableOut - prevCollectedHash chainhash.Hash - - // Used for tracking the live ticket pool. - originalParents map[chainhash.Hash]chainhash.Hash - immatureTickets []*stakeTicket - liveTickets []*stakeTicket - wonTickets map[chainhash.Hash][]*stakeTicket - expiredTickets []*stakeTicket -} - -// MakeGenerator returns a generator instance initialized with the genesis block -// as the tip as well as a cached generic pay-to-script-hash script for OP_TRUE. -func MakeGenerator(params *chaincfg.Params) (Generator, error) { - // Generate a generic pay-to-script-hash script that is a simple - // OP_TRUE. This allows the tests to avoid needing to generate and - // track actual public keys and signatures. - p2shOpTrueAddr, err := hcutil.NewAddressScriptHash(opTrueScript, params) - if err != nil { - return Generator{}, err - } - p2shOpTrueScript, err := txscript.PayToAddrScript(p2shOpTrueAddr) - if err != nil { - return Generator{}, err - } - - genesis := params.GenesisBlock - genesisHash := genesis.BlockHash() - return Generator{ - params: params, - tip: genesis, - tipName: "genesis", - blocks: map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis}, - blocksByName: map[string]*wire.MsgBlock{"genesis": genesis}, - p2shOpTrueAddr: p2shOpTrueAddr, - p2shOpTrueScript: p2shOpTrueScript, - originalParents: make(map[chainhash.Hash]chainhash.Hash), - wonTickets: make(map[chainhash.Hash][]*stakeTicket), - }, nil -} - -// Params returns the chain params associated with the generator instance. -func (g *Generator) Params() *chaincfg.Params { - return g.params -} - -// Tip returns the current tip block of the generator instance. -func (g *Generator) Tip() *wire.MsgBlock { - return g.tip -} - -// TipName returns the name of the current tip block of the generator instance. -func (g *Generator) TipName() string { - return g.tipName -} - -// BlockByName returns the block associated with the provided block name. It -// will panic if the specified block name does not exist. -func (g *Generator) BlockByName(blockName string) *wire.MsgBlock { - block, ok := g.blocksByName[blockName] - if !ok { - panic(fmt.Sprintf("block name %s does not exist", blockName)) - } - return block -} - -// BlockByHash returns the block associated with the provided block hash. It -// will panic if the specified block hash does not exist. -func (g *Generator) BlockByHash(hash *chainhash.Hash) *wire.MsgBlock { - block, ok := g.blocks[*hash] - if !ok { - panic(fmt.Sprintf("block with hash %s does not exist", hash)) - } - return block -} - -// opReturnScript returns a provably-pruneable OP_RETURN script with the -// provided data. -func opReturnScript(data []byte) []byte { - builder := txscript.NewScriptBuilder() - script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() - if err != nil { - panic(err) - } - return script -} - -// UniqueOpReturnScript returns a standard provably-pruneable OP_RETURN script -// with a random uint64 encoded as the data. -func UniqueOpReturnScript() []byte { - rand, err := wire.RandomUint64() - if err != nil { - panic(err) - } - - data := make([]byte, 8, 8) - binary.LittleEndian.PutUint64(data[0:8], rand) - return opReturnScript(data) -} - -// calcFullSubsidy returns the full block subsidy for the given block height. -// -// NOTE: This and the other subsidy calculation funcs intentionally are not -// using the blockchain code since the intent is to be able to generate known -// good tests which exercise that code, so it wouldn't make sense to use the -// same code to generate them. -//A(n) = (a1+(n-1)d)q^(n-1) -//S(n) = a1(1-q^n)/(1-q) + d[q(1-q^(n-1))/((1-q)^2) - (n-1)q^n/(1-q)] -//A(n) = A(n-1) *q + d*q^(n-1) - -func (g *Generator) calcFullSubsidy(blockHeight uint32) hcutil.Amount { - iterations := int64(blockHeight) / g.params.SubsidyReductionInterval - subsidy := g.params.BaseSubsidy - var q float64 = float64(g.params.MulSubsidy)/float64(g.params.DivSubsidy) - var temp float64 = 0.0 - - if iterations < 1682 { - temp = float64(g.params.BaseSubsidy) * (1.0 - float64(iterations) * 59363.0 / 100000000.0) * math.Pow(q,float64(iterations)) - }else{//after 99 years - temp = 100000000.0/float64(g.params.SubsidyReductionInterval) * math.Pow(0.1, float64(float64(iterations)-1681.0)) - } - subsidy = int64(temp) - return hcutil.Amount(subsidy) -} - -// calcPoWSubsidy returns the proof-of-work subsidy portion from a given full -// subsidy, block height, and number of votes that will be included in the -// block. -// -// NOTE: This and the other subsidy calculation funcs intentionally are not -// using the blockchain code since the intent is to be able to generate known -// good tests which exercise that code, so it wouldn't make sense to use the -// same code to generate them. -func (g *Generator) calcPoWSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount { - powProportion := hcutil.Amount(g.params.WorkRewardProportion) - totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) - powSubsidy := (fullSubsidy * powProportion) / totalProportions - if int64(blockHeight) < g.params.StakeValidationHeight { - return powSubsidy - } - - // Reduce the subsidy according to the number of votes. - ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock) - return (powSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock -} - -// calcPoSSubsidy returns the proof-of-stake subsidy portion for a given block -// height being voted on. -// -// NOTE: This and the other subsidy calculation funcs intentionally are not -// using the blockchain code since the intent is to be able to generate known -// good tests which exercise that code, so it wouldn't make sense to use the -// same code to generate them. -func (g *Generator) calcPoSSubsidy(heightVotedOn uint32) hcutil.Amount { - if int64(heightVotedOn+1) < g.params.StakeValidationHeight { - return 0 - } - - fullSubsidy := g.calcFullSubsidy(heightVotedOn) - posProportion := hcutil.Amount(g.params.StakeRewardProportion) - totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) - return (fullSubsidy * posProportion) / totalProportions -} - -// calcDevSubsidy returns the dev org subsidy portion from a given full subsidy. -// -// NOTE: This and the other subsidy calculation funcs intentionally are not -// using the blockchain code since the intent is to be able to generate known -// good tests which exercise that code, so it wouldn't make sense to use the -// same code to generate them. -func (g *Generator) calcDevSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount { - devProportion := hcutil.Amount(g.params.BlockTaxProportion) - totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) - devSubsidy := (fullSubsidy * devProportion) / totalProportions - if int64(blockHeight) < g.params.StakeValidationHeight { - return devSubsidy - } - - // Reduce the subsidy according to the number of votes. - ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock) - return (devSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock -} - -// standardCoinbaseOpReturnScript returns a standard script suitable for use as -// the second output of a standard coinbase transaction of a new block. In -// particular, the serialized data used with the OP_RETURN starts with the block -// height and is followed by 32 bytes which are treated as 4 uint64 extra -// nonces. This implementation puts a cryptographically random value into the -// final extra nonce position. The actual format of the data after the block -// height is not defined however this effectivley mirrors the actual mining code -// at the time it was written. -func standardCoinbaseOpReturnScript(blockHeight uint32) []byte { - rand, err := wire.RandomUint64() - if err != nil { - panic(err) - } - - data := make([]byte, 36, 36) - binary.LittleEndian.PutUint32(data[0:4], blockHeight) - binary.LittleEndian.PutUint64(data[28:36], rand) - return opReturnScript(data) -} - -// addCoinbaseTxOutputs adds the following outputs to the provided transaction -// which is assumed to be a coinbase transaction: -// - First output pays the development subsidy portion to the dev org -// - Second output is a standard provably prunable data-only coinbase output -// - Third and subsequent outputs pay the pow subsidy portion to the generic -// OP_TRUE p2sh script hash -func (g *Generator) addCoinbaseTxOutputs(tx *wire.MsgTx, blockHeight uint32, devSubsidy, powSubsidy hcutil.Amount) { - // First output is the developer subsidy. - tx.AddTxOut(&wire.TxOut{ - Value: int64(devSubsidy), - Version: g.params.OrganizationPkScriptVersion, - PkScript: g.params.OrganizationPkScript, - }) - - // Second output is a provably prunable data-only output that is used - // to ensure the coinbase is unique. - tx.AddTxOut(wire.NewTxOut(0, standardCoinbaseOpReturnScript(blockHeight))) - - // Final outputs are the proof-of-work subsidy split into more than one - // output. These are in turn used througout the tests as inputs to - // other transactions such as ticket purchases and additional spend - // transactions. - const numPoWOutputs = 6 - amount := powSubsidy / numPoWOutputs - for i := 0; i < numPoWOutputs; i++ { - if i == numPoWOutputs-1 { - amount = powSubsidy - amount*(numPoWOutputs-1) - } - tx.AddTxOut(wire.NewTxOut(int64(amount), g.p2shOpTrueScript)) - } -} - -// CreateCoinbaseTx returns a coinbase transaction paying an appropriate -// subsidy based on the passed block height and number of votes to the dev org -// and proof-of-work miner. -// -// See the addCoinbaseTxOutputs documentation for a breakdown of the outputs -// the transaction contains. -func (g *Generator) CreateCoinbaseTx(blockHeight uint32, numVotes uint16) *wire.MsgTx { - // Calculate the subsidy proportions based on the block height and the - // number of votes the block will include. - fullSubsidy := g.calcFullSubsidy(blockHeight) - devSubsidy := g.calcDevSubsidy(fullSubsidy, blockHeight, numVotes) - powSubsidy := g.calcPoWSubsidy(fullSubsidy, blockHeight, numVotes) - - tx := wire.NewMsgTx() - tx.AddTxIn(&wire.TxIn{ - // Coinbase transactions have no inputs, so previous outpoint is - // zero hash and max index. - PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, - wire.MaxPrevOutIndex, wire.TxTreeRegular), - Sequence: wire.MaxTxInSequenceNum, - ValueIn: int64(devSubsidy + powSubsidy), - BlockHeight: wire.NullBlockHeight, - BlockIndex: wire.NullBlockIndex, - SignatureScript: coinbaseSigScript, - }) - - g.addCoinbaseTxOutputs(tx, blockHeight, devSubsidy, powSubsidy) - - return tx -} - -// purchaseCommitmentScript returns a standard provably-pruneable OP_RETURN -// commitment script suitable for use in a ticket purchase tx (sstx) using the -// provided target address, amount, and fee limits. -func purchaseCommitmentScript(addr hcutil.Address, amount, voteFeeLimit, revocationFeeLimit hcutil.Amount) []byte { - // The limits are defined in terms of the closest base 2 exponent and - // a bit that must be set to specify the limit is to be applied. The - // vote fee exponent is in the bottom 8 bits, while the revocation fee - // exponent is in the upper 8 bits. - limits := uint16(0) - if voteFeeLimit != 0 { - exp := uint16(math.Ceil(math.Log2(float64(voteFeeLimit)))) - limits |= (exp | 0x40) - } - if revocationFeeLimit != 0 { - exp := uint16(math.Ceil(math.Log2(float64(revocationFeeLimit)))) - limits |= ((exp | 0x40) << 8) - } - - // The data consists of the 20-byte raw script address for the given - // address, 8 bytes for the amount to commit to (with the upper bit flag - // set to indicate a pay-to-script-hash address), and 2 bytes for the - // fee limits. - var data [30]byte - copy(data[:], addr.ScriptAddress()) - binary.LittleEndian.PutUint64(data[20:], uint64(amount)) - data[27] |= 1 << 7 - binary.LittleEndian.PutUint16(data[28:], limits) - script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). - AddData(data[:]).Script() - if err != nil { - panic(err) - } - return script -} - -// createTicketPurchaseTx creates a new transaction that spends the provided -// output to purchase a stake submission ticket (sstx) at the given ticket -// price. Both the ticket and the change will go to a p2sh script that is -// composed with a single OP_TRUE. -// -// The transaction consists of the following outputs: -// - First output is an OP_SSTX followed by the OP_TRUE p2sh script hash -// - Second output is an OP_RETURN followed by the commitment script -// - Third output is an OP_SSTXCHANGE followed by the OP_TRUE p2sh script hash -func (g *Generator) createTicketPurchaseTx(spend *SpendableOut, ticketPrice, fee hcutil.Amount) *wire.MsgTx { - // The first output is the voting rights address. This impl uses the - // standard pay-to-script-hash to an OP_TRUE. - pkScript, err := txscript.PayToSStx(g.p2shOpTrueAddr) - if err != nil { - panic(err) - } - - // Generate the commitment script. - commitScript := purchaseCommitmentScript(g.p2shOpTrueAddr, - ticketPrice+fee, 0, ticketPrice) - - // Calculate change and generate script to deliver it. - change := spend.amount - ticketPrice - fee - changeScript, err := txscript.PayToSStxChange(g.p2shOpTrueAddr) - if err != nil { - panic(err) - } - - // Generate and return the transaction spending from the provided - // spendable output with the previously described outputs. - tx := wire.NewMsgTx() - tx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: spend.prevOut, - Sequence: wire.MaxTxInSequenceNum, - ValueIn: int64(spend.amount), - BlockHeight: spend.blockHeight, - BlockIndex: spend.blockIndex, - SignatureScript: opTrueRedeemScript, - }) - tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript)) - tx.AddTxOut(wire.NewTxOut(0, commitScript)) - tx.AddTxOut(wire.NewTxOut(int64(change), changeScript)) - return tx -} - -// isTicketPurchaseTx returns whether or not the passed transaction is a stake -// ticket purchase. -// -// NOTE: Like many other functions in this test code, this function -// intentionally does not use the blockchain/stake package code since the intent -// is to be able to generate known good tests which exercise that code, so it -// wouldn't make sense to use the same code to generate them. It must also be -// noted that this function is NOT robust. It is the minimum necessary needed -// by the testing framework. -func isTicketPurchaseTx(tx *wire.MsgTx) bool { - if len(tx.TxOut) == 0 { - return false - } - txOut := tx.TxOut[0] - scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) - return scriptClass == txscript.StakeSubmissionTy -} - -// isVoteTx returns whether or not the passed tx is a stake vote (ssgen). -// -// NOTE: Like many other functions in this test code, this function -// intentionally does not use the blockchain/stake package code since the intent -// is to be able to generate known good tests which exercise that code, so it -// wouldn't make sense to use the same code to generate them. It must also be -// noted that this function is NOT robust. It is the minimum necessary needed -// by the testing framework. -func isVoteTx(tx *wire.MsgTx) bool { - if len(tx.TxOut) < 3 { - return false - } - txOut := tx.TxOut[2] - scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) - return scriptClass == txscript.StakeGenTy -} - -// isRevocationTx returns whether or not the passed tx is a stake ticket -// revocation (ssrtx). -// -// NOTE: Like many other functions in this test code, this function -// intentionally does not use the blockchain/stake package code since the intent -// is to be able to generate known good tests which exercise that code, so it -// wouldn't make sense to use the same code to generate them. It must also be -// noted that this function is NOT robust. It is the minimum necessary needed -// by the testing framework. -func isRevocationTx(tx *wire.MsgTx) bool { - if len(tx.TxOut) == 0 { - return false - } - txOut := tx.TxOut[0] - scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) - return scriptClass == txscript.StakeRevocationTy -} - -// voteBlockScript returns a standard provably-pruneable OP_RETURN script -// suitable for use in a vote tx (ssgen) given the block to vote on. -func voteBlockScript(parentBlock *wire.MsgBlock) []byte { - var data [36]byte - parentHash := parentBlock.BlockHash() - copy(data[:], parentHash[:]) - binary.LittleEndian.PutUint32(data[32:], parentBlock.Header.Height) - script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). - AddData(data[:]).Script() - if err != nil { - panic(err) - } - return script -} - -// voteBitsScript returns a standard provably-pruneable OP_RETURN script -// suitable for use in a vote tx (ssgen) with the appropriate vote bits set -// depending on the provided params. -func voteBitsScript(bits uint16, voteVersion uint32) []byte { - data := make([]byte, 6) - binary.LittleEndian.PutUint16(data[:], bits) - binary.LittleEndian.PutUint32(data[2:], voteVersion) - if voteVersion == 0 { - data = data[:2] - } - script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). - AddData(data[:]).Script() - if err != nil { - panic(err) - } - return script -} - -// createVoteTx returns a new transaction (ssgen) paying an appropriate subsidy -// for the given block height (and the number of votes per block) as well as the -// original commitments. -// -// The transaction consists of the following outputs: -// - First output is an OP_RETURN followed by the block hash and height -// - Second output is an OP_RETURN followed by the vote bits -// - Third and subsequent outputs are the payouts according to the ticket -// commitments and the appropriate proportion of the vote subsidy. -func (g *Generator) createVoteTx(parentBlock *wire.MsgBlock, ticket *stakeTicket) *wire.MsgTx { - // Calculate the proof-of-stake subsidy proportion based on the block - // height. - posSubsidy := g.calcPoSSubsidy(parentBlock.Header.Height) - voteSubsidy := posSubsidy / hcutil.Amount(g.params.TicketsPerBlock) - ticketPrice := hcutil.Amount(ticket.tx.TxOut[0].Value) - - // The first output is the block (hash and height) the vote is for. - blockScript := voteBlockScript(parentBlock) - - // The second output is the vote bits. - voteScript := voteBitsScript(voteBitYes, 0) - - // The third and subsequent outputs pay the original commitment amounts - // along with the appropriate portion of the vote subsidy. This impl - // uses the standard pay-to-script-hash to an OP_TRUE. - stakeGenScript, err := txscript.PayToSSGen(g.p2shOpTrueAddr) - if err != nil { - panic(err) - } - - // Generate and return the transaction with the proof-of-stake subsidy - // coinbase and spending from the provided ticket along with the - // previously described outputs. - ticketHash := ticket.tx.CachedTxHash() - tx := wire.NewMsgTx() - tx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, - wire.MaxPrevOutIndex, wire.TxTreeRegular), - Sequence: wire.MaxTxInSequenceNum, - ValueIn: int64(voteSubsidy), - BlockHeight: wire.NullBlockHeight, - BlockIndex: wire.NullBlockIndex, - SignatureScript: g.params.StakeBaseSigScript, - }) - tx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *wire.NewOutPoint(ticketHash, 0, - wire.TxTreeStake), - Sequence: wire.MaxTxInSequenceNum, - ValueIn: int64(ticketPrice), - BlockHeight: ticket.blockHeight, - BlockIndex: ticket.blockIndex, - SignatureScript: opTrueRedeemScript, - }) - tx.AddTxOut(wire.NewTxOut(0, blockScript)) - tx.AddTxOut(wire.NewTxOut(0, voteScript)) - tx.AddTxOut(wire.NewTxOut(int64(voteSubsidy+ticketPrice), stakeGenScript)) - return tx -} - -// ancestorBlock returns the ancestor block at the provided height by following -// the chain backwards from the given block. The returned block will be nil -// when a height is requested that is after the height of the passed block. -// Also, a callback can optionally be provided that is invoked with each block -// as it traverses. -func (g *Generator) ancestorBlock(block *wire.MsgBlock, height uint32, f func(*wire.MsgBlock)) *wire.MsgBlock { - // Nothing to do if the requested height is outside of the valid - // range. - if block == nil || height > block.Header.Height { - return nil - } - - // Iterate backwards until the requested height is reached. - for block != nil && block.Header.Height > height { - block = g.blocks[block.Header.PrevBlock] - if f != nil && block != nil { - f(block) - } - } - - return block -} - -// mergeDifficulty takes an original stake difficulty and two new, scaled -// stake difficulties, merges the new difficulties, and outputs a new -// merged stake difficulty. -func mergeDifficulty(oldDiff int64, newDiff1 int64, newDiff2 int64) int64 { - newDiff1Big := big.NewInt(newDiff1) - newDiff2Big := big.NewInt(newDiff2) - newDiff2Big.Lsh(newDiff2Big, 32) - - oldDiffBig := big.NewInt(oldDiff) - oldDiffBigLSH := big.NewInt(oldDiff) - oldDiffBigLSH.Lsh(oldDiffBig, 32) - - newDiff1Big.Div(oldDiffBigLSH, newDiff1Big) - newDiff2Big.Div(newDiff2Big, oldDiffBig) - - // Combine the two changes in difficulty. - summedChange := big.NewInt(0) - summedChange.Set(newDiff2Big) - summedChange.Lsh(summedChange, 32) - summedChange.Div(summedChange, newDiff1Big) - summedChange.Mul(summedChange, oldDiffBig) - summedChange.Rsh(summedChange, 32) - - return summedChange.Int64() -} - -// limitRetarget clamps the passed new difficulty to the old one adjusted by the -// factor specified in the chain parameters. This ensures the difficulty can -// only move up or down by a limited amount. -func (g *Generator) limitRetarget(oldDiff, newDiff int64) int64 { - maxRetarget := g.params.RetargetAdjustmentFactor - switch { - case newDiff == 0: - fallthrough - case (oldDiff / newDiff) > (maxRetarget - 1): - return oldDiff / maxRetarget - case (newDiff / oldDiff) > (maxRetarget - 1): - return oldDiff * maxRetarget - } - - return newDiff -} - -// calcNextRequiredDifficulty returns the required proof-of-work difficulty for -// the block after the current tip block the generator is associated with. -// -// An overview of the algorithm is as follows: -// 1) Use the proof-of-work limit for all blocks before the first retarget -// window -// 2) Use the previous block's difficulty if the next block is not at a retarget -// interval -// 3) Calculate the ideal retarget difficulty for each window based on the -// actual timespan of the window versus the target timespan and exponentially -// weight each difficulty such that the most recent window has the highest -// weight -// 4) Calculate the final retarget difficulty based on the exponential weighted -// average and ensure it is limited to the max retarget adjustment factor -func (g *Generator) calcNextRequiredDifficulty() uint32 { - // Target difficulty before the first retarget interval is the pow - // limit. - nextHeight := g.tip.Header.Height + 1 - windowSize := g.params.WorkDiffWindowSize - if int64(nextHeight) < windowSize { - return g.params.PowLimitBits - } - - // Return the previous block's difficulty requirements if the next block - // is not at a difficulty retarget interval. - curDiff := int64(g.tip.Header.Bits) - if int64(nextHeight)%windowSize != 0 { - return uint32(curDiff) - } - - // Calculate the ideal retarget difficulty for each window based on the - // actual time between blocks versus the target time and exponentially - // weight them. - adjustedTimespan := big.NewInt(0) - tempBig := big.NewInt(0) - weightedTimespanSum, weightSum := big.NewInt(0), big.NewInt(0) - targetTimespan := int64(g.params.TargetTimespan) - targetTimespanBig := big.NewInt(targetTimespan) - numWindows := g.params.WorkDiffWindows - weightAlpha := g.params.WorkDiffAlpha - block := g.tip - finalWindowTime := block.Header.Timestamp.UnixNano() - for i := int64(0); i < numWindows; i++ { - // Get the timestamp of the block at the start of the window and - // calculate the actual timespan accordingly. Use the target - // timespan if there are not yet enough blocks left to cover the - // window. - actualTimespan := targetTimespan - if int64(block.Header.Height) > windowSize { - for j := int64(0); j < windowSize; j++ { - block = g.blocks[block.Header.PrevBlock] - } - startWindowTime := block.Header.Timestamp.UnixNano() - actualTimespan = finalWindowTime - startWindowTime - - // Set final window time for the next window. - finalWindowTime = startWindowTime - } - - // Calculate the ideal retarget difficulty for the window based - // on the actual timespan and weight it exponentially by - // multiplying it by 2^(window_number) such that the most recent - // window receives the most weight. - // - // Also, since integer division is being used, shift up the - // number of new tickets 32 bits to avoid losing precision. - // - // windowWeightShift = ((numWindows - i) * weightAlpha) - // adjustedTimespan = (actualTimespan << 32) / targetTimespan - // weightedTimespanSum += adjustedTimespan << windowWeightShift - // weightSum += 1 << windowWeightShift - windowWeightShift := uint((numWindows - i) * weightAlpha) - adjustedTimespan.SetInt64(actualTimespan) - adjustedTimespan.Lsh(adjustedTimespan, 32) - adjustedTimespan.Div(adjustedTimespan, targetTimespanBig) - adjustedTimespan.Lsh(adjustedTimespan, windowWeightShift) - weightedTimespanSum.Add(weightedTimespanSum, adjustedTimespan) - weight := tempBig.SetInt64(1) - weight.Lsh(weight, windowWeightShift) - weightSum.Add(weightSum, weight) - } - - // Calculate the retarget difficulty based on the exponential weigthed - // average and shift the result back down 32 bits to account for the - // previous shift up in order to avoid losing precision. Then, limit it - // to the maximum allowed retarget adjustment factor. - // - // nextDiff = (weightedTimespanSum/weightSum * curDiff) >> 32 - curDiffBig := tempBig.SetInt64(curDiff) - weightedTimespanSum.Div(weightedTimespanSum, weightSum) - weightedTimespanSum.Mul(weightedTimespanSum, curDiffBig) - weightedTimespanSum.Rsh(weightedTimespanSum, 32) - nextDiff := weightedTimespanSum.Int64() - nextDiff = g.limitRetarget(curDiff, nextDiff) - - if nextDiff > int64(g.params.PowLimitBits) { - return g.params.PowLimitBits - } - return uint32(nextDiff) -} - -// calcNextRequiredStakeDifficulty returns the required stake difficulty (aka -// ticket price) for the block after the current tip block the generator is -// associated with. -// -// An overview of the algorithm is as follows: -// 1) Use the minimum value for any blocks before any tickets could have -// possibly been purchased due to coinbase maturity requirements -// 2) Return 0 if the current tip block stake difficulty is 0. This is a -// safety check against a condition that should never actually happen. -// 3) Use the previous block's difficulty if the next block is not at a retarget -// interval -// 4) Calculate the ideal retarget difficulty for each window based on the -// actual pool size in the window versus the target pool size skewed by a -// constant factor to weight the ticket pool size instead of the tickets per -// block and exponentially weight each difficulty such that the most recent -// window has the highest weight -// 5) Calculate the pool size retarget difficulty based on the exponential -// weighted average and ensure it is limited to the max retarget adjustment -// factor -- This is the first metric used to calculate the final difficulty -// 6) Calculate the ideal retarget difficulty for each window based on the -// actual new tickets in the window versus the target new tickets per window -// and exponentially weight each difficulty such that the most recent window -// has the highest weight -// 7) Calculate the tickets per window retarget difficulty based on the -// exponential weighted average and ensure it is limited to the max retarget -// adjustment factor -// 8) Calculate the final difficulty by averaging the pool size retarget -// difficulty from #5 and the tickets per window retarget difficulty from #7 -// using scaled multiplication and ensure it is limited to the max retarget -// adjustment factor -// -// NOTE: In order to simplify the test code, this implementation does not use -// big integers so it will NOT match the actual consensus code for really big -// numbers. However, the parameters on simnet and the pool sizes used in these -// tests are low enough that this is not an issue for the tests. Anyone looking -// at this code should NOT use it for mainnet calculations as is since it will -// not always yield the correct results. -func (g *Generator) calcNextRequiredStakeDifficulty() int64 { - // Stake difficulty before any tickets could possibly be purchased is - // the minimum value. - nextHeight := g.tip.Header.Height + 1 - stakeDiffStartHeight := uint32(g.params.CoinbaseMaturity) + 1 - if nextHeight < stakeDiffStartHeight { - return g.params.MinimumStakeDiff - } - - // Return 0 if the current difficulty is already zero since any scaling - // of 0 is still 0. This should never really happen since there is a - // minimum stake difficulty, but the consensus code checks the condition - // just in case, so follow suit here. - curDiff := g.tip.Header.SBits - if curDiff == 0 { - return 0 - } - - // Return the previous block's difficulty requirements if the next block - // is not at a difficulty retarget interval. - windowSize := g.params.StakeDiffWindowSize - if int64(nextHeight)%windowSize != 0 { - return curDiff - } - - // -------------------------------- - // Ideal pool size retarget metric. - // -------------------------------- - - // Calculate the ideal retarget difficulty for each window based on the - // actual pool size in the window versus the target pool size and - // exponentially weight them. - var weightedPoolSizeSum, weightSum uint64 - ticketsPerBlock := int64(g.params.TicketsPerBlock) - targetPoolSize := ticketsPerBlock * int64(g.params.TicketPoolSize) - numWindows := g.params.StakeDiffWindows - weightAlpha := g.params.StakeDiffAlpha - block := g.tip - for i := int64(0); i < numWindows; i++ { - // Get the pool size for the block at the start of the window. - // Use zero if there are not yet enough blocks left to cover the - // window. - prevRetargetHeight := nextHeight - uint32(windowSize*(i+1)) - windowPoolSize := int64(0) - block = g.ancestorBlock(block, prevRetargetHeight, nil) - if block != nil { - windowPoolSize = int64(block.Header.PoolSize) - } - - // Skew the pool size by the constant weight factor specified in - // the chain parameters (which is typically the max adjustment - // factor) in order to help weight the ticket pool size versus - // tickets per block. Also, ensure the skewed pool size is a - // minimum of 1. - skewedPoolSize := targetPoolSize + (windowPoolSize- - targetPoolSize)*int64(g.params.TicketPoolSizeWeight) - if skewedPoolSize <= 0 { - skewedPoolSize = 1 - } - - // Calculate the ideal retarget difficulty for the window based - // on the skewed pool size and weight it exponentially by - // multiplying it by 2^(window_number) such that the most recent - // window receives the most weight. - // - // Also, since integer division is being used, shift up the - // number of new tickets 32 bits to avoid losing precision. - // - // NOTE: The real algorithm uses big ints, but these purpose - // built tests won't be using large enough values to overflow, - // so just use uint64s. - adjusted := (skewedPoolSize << 32) / targetPoolSize - adjusted = adjusted << uint64((numWindows-i)*weightAlpha) - weightedPoolSizeSum += uint64(adjusted) - weightSum += 1 << uint64((numWindows-i)*weightAlpha) - } - - // Calculate the pool size retarget difficulty based on the exponential - // weigthed average and shift the result back down 32 bits to account - // for the previous shift up in order to avoid losing precision. Then, - // limit it to the maximum allowed retarget adjustment factor. - // - // This is the first metric used in the final calculated difficulty. - nextPoolSizeDiff := (int64(weightedPoolSizeSum/weightSum) * curDiff) >> 32 - nextPoolSizeDiff = g.limitRetarget(curDiff, nextPoolSizeDiff) - - // ----------------------------------------- - // Ideal tickets per window retarget metric. - // ----------------------------------------- - - // Calculate the ideal retarget difficulty for each window based on the - // actual number of new tickets in the window versus the target tickets - // per window and exponentially weight them. - var weightedTicketsSum uint64 - targetTicketsPerWindow := ticketsPerBlock * windowSize - block = g.tip - for i := int64(0); i < numWindows; i++ { - // Since the difficulty for the next block after the current tip - // is being calculated and there is no such block yet, the sum - // of all new tickets in the first window needs to start with - // the number of new tickets in the tip block. - var windowNewTickets int64 - if i == 0 { - windowNewTickets = int64(block.Header.FreshStake) - } - - // Tally all of the new tickets in all blocks in the window and - // ensure the number of new tickets is a minimum of 1. - prevRetargetHeight := nextHeight - uint32(windowSize*(i+1)) - block = g.ancestorBlock(block, prevRetargetHeight, func(blk *wire.MsgBlock) { - windowNewTickets += int64(blk.Header.FreshStake) - }) - if windowNewTickets <= 0 { - windowNewTickets = 1 - } - - // Calculate the ideal retarget difficulty for the window based - // on the number of new tickets and weight it exponentially by - // multiplying it by 2^(window_number) such that the most recent - // window receives the most weight. - // - // Also, since integer division is being used, shift up the - // number of new tickets 32 bits to avoid losing precision. - // - // NOTE: The real algorithm uses big ints, but these purpose - // built tests won't be using large enough values to overflow, - // so just use uint64s. - adjusted := (windowNewTickets << 32) / targetTicketsPerWindow - adjusted = adjusted << uint64((numWindows-i)*weightAlpha) - weightedTicketsSum += uint64(adjusted) - } - - // Calculate the tickets per window retarget difficulty based on the - // exponential weighted average and shift the result back down 32 bits - // to account for the previous shift up in order to avoid losing - // precision. Then, limit it to the maximum allowed retarget adjustment - // factor. - // - // This is the second metric used in the final calculated difficulty. - nextNewTixDiff := (int64(weightedTicketsSum/weightSum) * curDiff) >> 32 - nextNewTixDiff = g.limitRetarget(curDiff, nextNewTixDiff) - - // Average the previous two metrics using scaled multiplication and - // ensure the result is limited to both the maximum allowed retarget - // adjustment factor and the minimum allowed stake difficulty. - nextDiff := mergeDifficulty(curDiff, nextPoolSizeDiff, nextNewTixDiff) - nextDiff = g.limitRetarget(curDiff, nextDiff) - if nextDiff < g.params.MinimumStakeDiff { - return g.params.MinimumStakeDiff - } - return nextDiff -} - -// hash256prng is a determinstic pseudorandom number generator that uses a -// 256-bit secure hashing function to generate random uint32s starting from -// an initial seed. -type hash256prng struct { - seed chainhash.Hash // Initialization seed - idx uint64 // Hash iterator index - cachedHash chainhash.Hash // Most recently generated hash - hashOffset int // Offset into most recently generated hash -} - -// newHash256PRNG creates a pointer to a newly created hash256PRNG. -func newHash256PRNG(seed []byte) *hash256prng { - // The provided seed is initialized by appending a constant derived from - // the hex representation of pi and hashing the result to give 32 bytes. - // This ensures the PRNG is always doing a short number of rounds - // regardless of input since it will only need to hash small messages - // (less than 64 bytes). - seedHash := chainhash.HashFunc(append(seed, hash256prngSeedConst...)) - return &hash256prng{ - seed: seedHash, - idx: 0, - cachedHash: seedHash, - } -} - -// State returns a hash that represents the current state of the deterministic -// PRNG. -func (hp *hash256prng) State() chainhash.Hash { - // The final state is the hash of the most recently generated hash - // concatenated with both the hash iterator index and the offset into - // the hash. - // - // hash(hp.cachedHash || hp.idx || hp.hashOffset) - finalState := make([]byte, len(hp.cachedHash)+4+1) - copy(finalState, hp.cachedHash[:]) - offset := len(hp.cachedHash) - binary.BigEndian.PutUint32(finalState[offset:], uint32(hp.idx)) - offset += 4 - finalState[offset] = byte(hp.hashOffset) - return chainhash.HashH(finalState) -} - -// Hash256Rand returns a uint32 random number using the pseudorandom number -// generator and updates the state. -func (hp *hash256prng) Hash256Rand() uint32 { - offset := hp.hashOffset * 4 - r := binary.BigEndian.Uint32(hp.cachedHash[offset : offset+4]) - hp.hashOffset++ - - // Generate a new hash and reset the hash position index once it would - // overflow the available bytes in the most recently generated hash. - if hp.hashOffset > 7 { - // Hash of the seed concatenated with the hash iterator index. - // hash(hp.seed || hp.idx) - data := make([]byte, len(hp.seed)+4) - copy(data, hp.seed[:]) - binary.BigEndian.PutUint32(data[len(hp.seed):], uint32(hp.idx)) - hp.cachedHash = chainhash.HashH(data) - hp.idx++ - hp.hashOffset = 0 - } - - // Roll over the entire PRNG by re-hashing the seed when the hash - // iterator index overlows a uint32. - if hp.idx > math.MaxUint32 { - hp.seed = chainhash.HashH(hp.seed[:]) - hp.cachedHash = hp.seed - hp.idx = 0 - } - - return r -} - -// uniformRandom returns a random in the range [0, upperBound) while avoiding -// modulo bias to ensure a normal distribution within the specified range. -func (hp *hash256prng) uniformRandom(upperBound uint32) uint32 { - if upperBound < 2 { - return 0 - } - - // (2^32 - (x*2)) % x == 2^32 % x when x <= 2^31 - min := ((math.MaxUint32 - (upperBound * 2)) + 1) % upperBound - if upperBound > 0x80000000 { - min = 1 + ^upperBound - } - - r := hp.Hash256Rand() - for r < min { - r = hp.Hash256Rand() - } - return r % upperBound -} - -// winningTickets returns a slice of tickets that are required to vote for the -// given block being voted on and live ticket pool and the associated underlying -// deterministic prng state hash. -func winningTickets(voteBlock *wire.MsgBlock, liveTickets []*stakeTicket, numVotes uint16) ([]*stakeTicket, chainhash.Hash, error) { - // Serialize the parent block header used as the seed to the - // deterministic pseudo random number generator for vote selection. - var buf bytes.Buffer - buf.Grow(wire.MaxBlockHeaderPayload) - if err := voteBlock.Header.Serialize(&buf); err != nil { - return nil, chainhash.Hash{}, err - } - - // Ensure the number of live tickets is within the allowable range. - numLiveTickets := uint32(len(liveTickets)) - if numLiveTickets > math.MaxUint32 { - return nil, chainhash.Hash{}, fmt.Errorf("live ticket pool "+ - "has %d tickets which is more than the max allowed of "+ - "%d", len(liveTickets), uint32(math.MaxUint32)) - } - if uint32(numVotes) > numLiveTickets { - return nil, chainhash.Hash{}, fmt.Errorf("live ticket pool "+ - "has %d tickets, while %d are needed to vote", - len(liveTickets), numVotes) - } - - // Construct list of winners by generating successive values from the - // deterministic prng and using them as indices into the sorted live - // ticket pool while skipping any duplicates that might occur. - prng := newHash256PRNG(buf.Bytes()) - winners := make([]*stakeTicket, 0, numVotes) - usedOffsets := make(map[uint32]struct{}) - for uint16(len(winners)) < numVotes { - ticketIndex := prng.uniformRandom(numLiveTickets) - if _, exists := usedOffsets[ticketIndex]; !exists { - usedOffsets[ticketIndex] = struct{}{} - winners = append(winners, liveTickets[ticketIndex]) - } - } - return winners, prng.State(), nil -} - -// calcFinalLotteryState calculates the final lottery state for a set of winning -// tickets and the associated deterministic prng state hash after selecting the -// winners. It is the first 6 bytes of: -// blake256(firstTicketHash || ... || lastTicketHash || prngStateHash) -func calcFinalLotteryState(winners []*stakeTicket, prngStateHash chainhash.Hash) [6]byte { - data := make([]byte, (len(winners)+1)*chainhash.HashSize) - for i := 0; i < len(winners); i++ { - h := winners[i].tx.CachedTxHash() - copy(data[chainhash.HashSize*i:], h[:]) - } - copy(data[chainhash.HashSize*len(winners):], prngStateHash[:]) - dataHash := chainhash.HashH(data) - - var finalState [6]byte - copy(finalState[:], dataHash[0:6]) - return finalState -} - -// calcMerkleRoot creates a merkle tree from the slice of transactions and -// returns the root of the tree. -func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { - utilTxns := make([]*hcutil.Tx, 0, len(txns)) - for _, tx := range txns { - utilTxns = append(utilTxns, hcutil.NewTx(tx)) - } - merkles := blockchain.BuildMerkleTreeStore(utilTxns) - return *merkles[len(merkles)-1] -} - -// solveBlock attempts to find a nonce which makes the passed block header hash -// to a value less than the target difficulty. When a successful solution is -// found, true is returned and the nonce field of the passed header is updated -// with the solution. False is returned if no solution exists. -// -// NOTE: This function will never solve blocks with a nonce of 0. This is done -// so the 'NextBlock' function can properly detect when a nonce was modified by -// a munge function. -func solveBlock(header *wire.BlockHeader) bool { - // sbResult is used by the solver goroutines to send results. - type sbResult struct { - found bool - nonce uint32 - } - - // solver accepts a block header and a nonce range to test. It is - // intended to be run as a goroutine. - targetDifficulty := blockchain.CompactToBig(header.Bits) - quit := make(chan bool) - results := make(chan sbResult) - solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { - // We need to modify the nonce field of the header, so make sure - // we work with a copy of the original header. - for i := startNonce; i >= startNonce && i <= stopNonce; i++ { - select { - case <-quit: - results <- sbResult{false, 0} - return - default: - hdr.Nonce = i - hash := hdr.BlockHash() - if blockchain.HashToBig(&hash).Cmp( - targetDifficulty) <= 0 { - - results <- sbResult{true, i} - return - } - } - } - results <- sbResult{false, 0} - } - - startNonce := uint32(1) - stopNonce := uint32(math.MaxUint32) - numCores := uint32(runtime.NumCPU()) - noncesPerCore := (stopNonce - startNonce) / numCores - for i := uint32(0); i < numCores; i++ { - rangeStart := startNonce + (noncesPerCore * i) - rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1 - if i == numCores-1 { - rangeStop = stopNonce - } - go solver(*header, rangeStart, rangeStop) - } - var foundResult bool - for i := uint32(0); i < numCores; i++ { - result := <-results - if !foundResult && result.found { - close(quit) - header.Nonce = result.nonce - foundResult = true - } - } - - return foundResult -} - -// ReplaceWithNVotes returns a function that itself takes a block and modifies -// it by replacing the votes in the stake tree with specified number of votes. -// -// NOTE: This must only be used as a munger to the 'NextBlock' function or it -// will lead to an invalid live ticket pool. To help safeguard against improper -// usage, it will panic if called with a block that does not connect to the -// current tip block. -func (g *Generator) ReplaceWithNVotes(numVotes uint16) func(*wire.MsgBlock) { - return func(b *wire.MsgBlock) { - // Attempt to prevent misuse of this function by ensuring the - // provided block connects to the current tip. - if b.Header.PrevBlock != g.tip.BlockHash() { - panic(fmt.Sprintf("attempt to replace number of votes "+ - "for block %s with parent %s that is not the "+ - "current tip %s", b.BlockHash(), - b.Header.PrevBlock, g.tip.BlockHash())) - } - - // Get the winning tickets for the specified number of votes. - parentBlock := g.tip - winners, _, err := winningTickets(parentBlock, g.liveTickets, - numVotes) - if err != nil { - panic(err) - } - - // Generate vote transactions for the winning tickets. - defaultNumVotes := int(g.params.TicketsPerBlock) - numExisting := len(b.STransactions) - defaultNumVotes - stakeTxns := make([]*wire.MsgTx, 0, numExisting+int(numVotes)) - for _, ticket := range winners { - voteTx := g.createVoteTx(parentBlock, ticket) - stakeTxns = append(stakeTxns, voteTx) - } - - // Add back the original stake transactions other than the - // original stake votes that have been replaced. - stakeTxns = append(stakeTxns, b.STransactions[defaultNumVotes:]...) - - // Update the block with the new stake transactions and the - // header with the new number of votes. - b.STransactions = stakeTxns - b.Header.Voters = numVotes - - // Recalculate the coinbase amount based on the number of new - // votes and update the coinbase so that the adjustment in - // subsidy is accounted for. - height := b.Header.Height - fullSubsidy := g.calcFullSubsidy(height) - devSubsidy := g.calcDevSubsidy(fullSubsidy, height, numVotes) - powSubsidy := g.calcPoWSubsidy(fullSubsidy, height, numVotes) - cbTx := b.Transactions[0] - cbTx.TxIn[0].ValueIn = int64(devSubsidy + powSubsidy) - cbTx.TxOut = nil - g.addCoinbaseTxOutputs(cbTx, height, devSubsidy, powSubsidy) - } -} - -// ReplaceBlockVersion returns a function that itself takes a block and modifies -// it by replacing the stake version of the header. -func ReplaceBlockVersion(newVersion int32) func(*wire.MsgBlock) { - return func(b *wire.MsgBlock) { - b.Header.Version = newVersion - } -} - -// ReplaceStakeVersion returns a function that itself takes a block and modifies -// it by replacing the stake version of the header. -func ReplaceStakeVersion(newVersion uint32) func(*wire.MsgBlock) { - return func(b *wire.MsgBlock) { - b.Header.StakeVersion = newVersion - } -} - -// ReplaceVoteVersions returns a function that itself takes a block and modifies -// it by replacing the voter version of the stake transactions. -// -// NOTE: This must only be used as a munger to the 'NextBlock' function or it -// will lead to an invalid live ticket pool. -func ReplaceVoteVersions(newVersion uint32) func(*wire.MsgBlock) { - return func(b *wire.MsgBlock) { - for _, stx := range b.STransactions { - if isVoteTx(stx) { - stx.TxOut[1].PkScript = voteBitsScript( - voteBitYes, newVersion) - } - } - } -} - -// ReplaceVotes returns a function that itself takes a block and modifies it by -// replacing the voter version and bits of the stake transactions. -// -// NOTE: This must only be used as a munger to the 'NextBlock' function or it -// will lead to an invalid live ticket pool. -func ReplaceVotes(voteBits uint16, newVersion uint32) func(*wire.MsgBlock) { - return func(b *wire.MsgBlock) { - for _, stx := range b.STransactions { - if isVoteTx(stx) { - stx.TxOut[1].PkScript = voteBitsScript(voteBits, - newVersion) - } - } - } -} - -// CreateSpendTx creates a transaction that spends from the provided spendable -// output and includes an additional unique OP_RETURN output to ensure the -// transaction ends up with a unique hash. The public key script is a simple -// OP_TRUE p2sh script which avoids the need to track addresses and signature -// scripts in the tests. The signature script is the opTrueRedeemScript. -func (g *Generator) CreateSpendTx(spend *SpendableOut, fee hcutil.Amount) *wire.MsgTx { - spendTx := wire.NewMsgTx() - spendTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: spend.prevOut, - Sequence: wire.MaxTxInSequenceNum, - ValueIn: int64(spend.amount), - BlockHeight: spend.blockHeight, - BlockIndex: spend.blockIndex, - SignatureScript: opTrueRedeemScript, - }) - spendTx.AddTxOut(wire.NewTxOut(int64(spend.amount-fee), - g.p2shOpTrueScript)) - spendTx.AddTxOut(wire.NewTxOut(0, UniqueOpReturnScript())) - return spendTx -} - -// CreateSpendTxForTx creates a transaction that spends from the first output of -// the provided transaction and includes an additional unique OP_RETURN output -// to ensure the transaction ends up with a unique hash. The public key script -// is a simple OP_TRUE p2sh script which avoids the need to track addresses and -// signature scripts in the tests. This signature script the -// opTrueRedeemScript. -func (g *Generator) CreateSpendTxForTx(tx *wire.MsgTx, blockHeight, txIndex uint32, fee hcutil.Amount) *wire.MsgTx { - spend := MakeSpendableOutForTx(tx, blockHeight, txIndex, 0) - return g.CreateSpendTx(&spend, fee) -} - -// removeTicket removes the passed index from the provided slice of tickets and -// returns the resulting slice. This is an in-place modification. -func removeTicket(tickets []*stakeTicket, index int) []*stakeTicket { - copy(tickets[index:], tickets[index+1:]) - tickets[len(tickets)-1] = nil // Prevent memory leak - tickets = tickets[:len(tickets)-1] - return tickets -} - -// connectLiveTickets updates the live ticket pool for a new tip block by -// removing tickets that are now expired from it, removing the passed winners -// from it, adding any immature tickets which are now mature to it, and -// resorting it. -func (g *Generator) connectLiveTickets(blockHash *chainhash.Hash, height uint32, winners, purchases []*stakeTicket) { - // Move expired tickets from the live ticket pool to the expired ticket - // pool. - ticketMaturity := uint32(g.params.TicketMaturity) - ticketExpiry := g.params.TicketExpiry - for i := 0; i < len(g.liveTickets); i++ { - ticket := g.liveTickets[i] - liveHeight := ticket.blockHeight + ticketMaturity - expireHeight := liveHeight + ticketExpiry - if height >= expireHeight { - g.liveTickets = removeTicket(g.liveTickets, i) - g.expiredTickets = append(g.expiredTickets, ticket) - - // This is required because the ticket at the current - // offset was just removed from the slice that is being - // iterated, so adjust the offset down one accordingly. - i-- - } - } - - // Move winning tickets from the live ticket pool to won tickets pool. - for i := 0; i < len(g.liveTickets); i++ { - ticket := g.liveTickets[i] - for _, winner := range winners { - if ticket.tx.CachedTxHash() == winner.tx.CachedTxHash() { - g.liveTickets = removeTicket(g.liveTickets, i) - - // This is required because the ticket at the - // current offset was just removed from the - // slice that is being iterated, so adjust the - // offset down one accordingly. - i-- - break - } - } - } - g.wonTickets[*blockHash] = winners - - // Move immature tickets which are now mature to the live ticket pool. - for i := 0; i < len(g.immatureTickets); i++ { - ticket := g.immatureTickets[i] - liveHeight := ticket.blockHeight + ticketMaturity - if height >= liveHeight { - g.immatureTickets = removeTicket(g.immatureTickets, i) - g.liveTickets = append(g.liveTickets, ticket) - - // This is required because the ticket at the current - // offset was just removed from the slice that is being - // iterated, so adjust the offset down one accordingly. - i-- - } - } - - // Resort the ticket pool now that all live ticket pool manipulations - // are done. - sort.Sort(stakeTicketSorter(g.liveTickets)) - - // Add new ticket purchases to the immature ticket pool. - g.immatureTickets = append(g.immatureTickets, purchases...) -} - -// connectBlockTickets updates the live ticket pool and associated data structs -// by for the passed block. It will panic if the specified block does not -// connect to the current tip block. -func (g *Generator) connectBlockTickets(b *wire.MsgBlock) { - // Attempt to prevent misuse of this function by ensuring the provided - // block connects to the current tip. - if b.Header.PrevBlock != g.tip.BlockHash() { - panic(fmt.Sprintf("attempt to connect block %s with parent %s "+ - "that is not the current tip %s", b.BlockHash(), - b.Header.PrevBlock, g.tip.BlockHash())) - } - - // Get all of the winning tickets for the block. - numVotes := g.params.TicketsPerBlock - winners, _, err := winningTickets(g.tip, g.liveTickets, numVotes) - if err != nil { - panic(err) - } - - // Extract the ticket purchases (sstx) from the block. - height := b.Header.Height - var purchases []*stakeTicket - for txIdx, tx := range b.STransactions { - if isTicketPurchaseTx(tx) { - ticket := &stakeTicket{tx, height, uint32(txIdx)} - purchases = append(purchases, ticket) - } - } - - // Update the live ticket pool and associated data structures. - blockHash := b.BlockHash() - g.connectLiveTickets(&blockHash, height, winners, purchases) -} - -// disconnectBlockTickets updates the live ticket pool and associated data -// structs by unwinding the passed block, which must be the current tip block. -// It will panic if the specified block is not the current tip block. -func (g *Generator) disconnectBlockTickets(b *wire.MsgBlock) { - // Attempt to prevent misuse of this function by ensuring the provided - // block is the current tip. - if b != g.tip { - panic(fmt.Sprintf("attempt to disconnect block %s that is not "+ - "the current tip %s", b.BlockHash(), g.tip.BlockHash())) - } - - // Remove tickets created in the block from the immature ticket pool. - blockHeight := b.Header.Height - for i := 0; i < len(g.immatureTickets); i++ { - ticket := g.immatureTickets[i] - if ticket.blockHeight == blockHeight { - g.immatureTickets = removeTicket(g.immatureTickets, i) - - // This is required because the ticket at the current - // offset was just removed from the slice that is being - // iterated, so adjust the offset down one accordingly. - i-- - } - } - - // Move tickets that are no longer mature from the live ticket pool to - // the immature ticket pool. - prevBlockHeight := blockHeight - 1 - ticketMaturity := uint32(g.params.TicketMaturity) - for i := 0; i < len(g.liveTickets); i++ { - ticket := g.liveTickets[i] - liveHeight := ticket.blockHeight + ticketMaturity - if prevBlockHeight < liveHeight { - g.liveTickets = removeTicket(g.liveTickets, i) - g.immatureTickets = append(g.immatureTickets, ticket) - - // This is required because the ticket at the current - // offset was just removed from the slice that is being - // iterated, so adjust the offset down one accordingly. - i-- - } - } - - // Move tickets that are no longer expired from the expired ticket pool - // to the live ticket pool. - ticketExpiry := g.params.TicketExpiry - for i := 0; i < len(g.expiredTickets); i++ { - ticket := g.expiredTickets[i] - liveHeight := ticket.blockHeight + ticketMaturity - expireHeight := liveHeight + ticketExpiry - if prevBlockHeight < expireHeight { - g.expiredTickets = removeTicket(g.expiredTickets, i) - g.liveTickets = append(g.liveTickets, ticket) - - // This is required because the ticket at the current - // offset was just removed from the slice that is being - // iterated, so adjust the offset down one accordingly. - i-- - } - } - - // Add the winning tickets consumed by the block back to the live ticket - // pool. - blockHash := b.BlockHash() - g.liveTickets = append(g.liveTickets, g.wonTickets[blockHash]...) - delete(g.wonTickets, blockHash) - - // Resort the ticket pool now that all live ticket pool manipulations - // are done. - sort.Sort(stakeTicketSorter(g.liveTickets)) -} - -// originalParent returns the original block the passed block was built from. -// This is necessary because callers might change the previous block hash in a -// munger which would cause the like ticket pool to be reconstructed improperly. -func (g *Generator) originalParent(b *wire.MsgBlock) *wire.MsgBlock { - parentHash, ok := g.originalParents[b.BlockHash()] - if !ok { - parentHash = b.Header.PrevBlock - } - return g.BlockByHash(&parentHash) -} - -// SetTip changes the tip of the instance to the block with the provided name. -// This is useful since the tip is used for things such as generating subsequent -// blocks. -func (g *Generator) SetTip(blockName string) { - // Nothing to do if already the tip. - if blockName == g.tipName { - return - } - - newTip := g.blocksByName[blockName] - if newTip == nil { - panic(fmt.Sprintf("tip block name %s does not exist", blockName)) - } - - // Create a list of blocks to disconnect and blocks to connect in order - // to switch to the new tip. - var connect, disconnect []*wire.MsgBlock - oldBranch, newBranch := g.tip, newTip - for oldBranch != newBranch { - // As long as the two branches are not at the same height, add - // the tip of the longest one to the appropriate connect or - // disconnect list and move its tip back to its previous block. - if oldBranch.Header.Height > newBranch.Header.Height { - disconnect = append(disconnect, oldBranch) - oldBranch = g.originalParent(oldBranch) - continue - } else if newBranch.Header.Height > oldBranch.Header.Height { - connect = append(connect, newBranch) - newBranch = g.originalParent(newBranch) - continue - } - - // At this point the two branches have the same height, so add - // each tip to the appropriate connect or disconnect list and - // the tips to their previous block. - disconnect = append(disconnect, oldBranch) - oldBranch = g.originalParent(oldBranch) - connect = append(connect, newBranch) - newBranch = g.originalParent(newBranch) - } - - // Update the live ticket pool and associated data structs by - // disconnecting all blocks back to the fork point. - for _, block := range disconnect { - g.disconnectBlockTickets(block) - g.tip = g.originalParent(block) - } - - // Update the live ticket pool and associated data structs by connecting - // all blocks after the fork point up to the new tip. The list of - // blocks to connect is iterated in reverse order, because it was - // constructed in reverse, and the blocks need to be connected in the - // order in which they build the chain. - for i := len(connect) - 1; i >= 0; i-- { - block := connect[i] - g.connectBlockTickets(block) - g.tip = block - } - - // Ensure the tip is the expected new tip and set the associated name. - if g.tip != newTip { - panic(fmt.Sprintf("tip %s is not expected new tip %s", - g.tip.BlockHash(), newTip.BlockHash())) - } - g.tipName = blockName -} - -// NextBlock builds a new block that extends the current tip associated with the -// generator and updates the generator's tip to the newly generated block. -// -// The block will include the following: -// - A coinbase with the following outputs: -// - One that pays the required 10% subsidy to the dev org -// - One that contains a standard coinbase OP_RETURN script -// - Six that pay the required 60% subsidy to an OP_TRUE p2sh script -// - When a spendable output is provided: -// - A transaction that spends from the provided output the following outputs: -// - One that pays the inputs amount minus 1 atom to an OP_TRUE p2sh script -// - Once the coinbase maturity has been reached: -// - A ticket purchase transaction (sstx) for each provided ticket spendable -// output with the following outputs: -// - One OP_SSTX output that grants voting rights to an OP_TRUE p2sh script -// - One OP_RETURN output that contains the required commitment and pays -// the subsidy to an OP_TRUE p2sh script -// - One OP_SSTXCHANGE output that sends change to an OP_TRUE p2sh script -// - Once the stake validation height has been reached: -// - 5 vote transactions (ssgen) as required according to the live ticket -// pool and vote selection rules with the following outputs: -// - One OP_RETURN followed by the block hash and height being voted on -// - One OP_RETURN followed by the vote bits -// - One or more OP_SSGEN outputs with the payouts according to the original -// ticket commitments -// -// Additionally, if one or more munge functions are specified, they will be -// invoked with the block prior to solving it. This provides callers with the -// opportunity to modify the block which is especially useful for testing. -// -// In order to simply the logic in the munge functions, the following rules are -// applied after all munge functions have been invoked: -// - The merkle root will be recalculated unless it was manually changed -// - The stake root will be recalculated unless it was manually changed -// - The size of the block will be recalculated unless it was manually changed -// - The block will be solved unless the nonce was changed -func (g *Generator) NextBlock(blockName string, spend *SpendableOut, ticketSpends []SpendableOut, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock { - // Calculate the next required stake difficulty (aka ticket price). - ticketPrice := hcutil.Amount(g.calcNextRequiredStakeDifficulty()) - - // Generate the appropriate votes and ticket purchases based on the - // current tip block and provided ticket spendable outputs. - var ticketWinners []*stakeTicket - var stakeTxns []*wire.MsgTx - var finalState [6]byte - nextHeight := g.tip.Header.Height + 1 - if nextHeight > uint32(g.params.CoinbaseMaturity) { - // Generate votes once the stake validation height has been - // reached. - if int64(nextHeight) >= g.params.StakeValidationHeight { - // Generate and add the vote transactions for the - // winning tickets to the stake tree. - numVotes := g.params.TicketsPerBlock - winners, stateHash, err := winningTickets(g.tip, - g.liveTickets, numVotes) - if err != nil { - panic(err) - } - ticketWinners = winners - for _, ticket := range winners { - voteTx := g.createVoteTx(g.tip, ticket) - stakeTxns = append(stakeTxns, voteTx) - } - - // Calculate the final lottery state hash for use in the - // block header. - finalState = calcFinalLotteryState(winners, stateHash) - } - - // Generate ticket purchases (sstx) using the provided spendable - // outputs. - if ticketSpends != nil { - const ticketFee = hcutil.Amount(2) - for i := 0; i < len(ticketSpends); i++ { - out := &ticketSpends[i] - purchaseTx := g.createTicketPurchaseTx(out, - ticketPrice, ticketFee) - stakeTxns = append(stakeTxns, purchaseTx) - } - } - } - - // Create stake tickets for the ticket purchases (sstx), count the - // votes (ssgen) and ticket revocations (ssrtx), and calculate the - // total PoW fees generated by the stake transactions. - var numVotes uint16 - var numTicketRevocations uint8 - var ticketPurchases []*stakeTicket - var stakeTreeFees hcutil.Amount - for txIdx, tx := range stakeTxns { - switch { - case isVoteTx(tx): - numVotes++ - case isTicketPurchaseTx(tx): - ticket := &stakeTicket{tx, nextHeight, uint32(txIdx)} - ticketPurchases = append(ticketPurchases, ticket) - case isRevocationTx(tx): - numTicketRevocations++ - } - - // Calculate any fees for the transaction. - var inputSum, outputSum hcutil.Amount - for _, txIn := range tx.TxIn { - inputSum += hcutil.Amount(txIn.ValueIn) - } - for _, txOut := range tx.TxOut { - outputSum += hcutil.Amount(txOut.Value) - } - stakeTreeFees += (inputSum - outputSum) - } - - // Create a standard coinbase and spending transaction. - var regularTxns []*wire.MsgTx - { - // Create coinbase transaction for the block with no additional - // dev or pow subsidy. - coinbaseTx := g.CreateCoinbaseTx(nextHeight, numVotes) - regularTxns = []*wire.MsgTx{coinbaseTx} - - // Increase the PoW subsidy to account for any fees in the stake - // tree. - coinbaseTx.TxOut[2].Value += int64(stakeTreeFees) - - // Create a transaction to spend the provided utxo if needed. - if spend != nil { - // Create the transaction with a fee of 1 atom for the - // miner and increase the PoW subsidy accordingly. - fee := hcutil.Amount(1) - coinbaseTx.TxOut[2].Value += int64(fee) - - // Create a transaction that spends from the provided - // spendable output and includes an additional unique - // OP_RETURN output to ensure the transaction ends up - // with a unique hash, then add it to the list of - // transactions to include in the block. The script is - // a simple OP_TRUE p2sh script in order to avoid the - // need to track addresses and signature scripts in the - // tests. - spendTx := g.CreateSpendTx(spend, fee) - regularTxns = append(regularTxns, spendTx) - } - } - - // Use a timestamp that is 7/8 of target timespan after the previous - // block unless this is the first block in which case the current time - // is used or the proof-of-work difficulty parameters have been adjusted - // such that it's greater than the max 2 hours worth of blocks that can - // be tested in which case one second is used. This helps maintain the - // retarget difficulty low as needed. Also, ensure the timestamp is - // limited to one second precision. - var ts time.Time - if nextHeight == 1 { - ts = time.Now() - } else { - if g.params.WorkDiffWindowSize > 7200 { - ts = g.tip.Header.Timestamp.Add(time.Second) - } else { - addDuration := g.params.TargetTimespan * 7 / 8 - ts = g.tip.Header.Timestamp.Add(addDuration) - } - } - ts = time.Unix(ts.Unix(), 0) - - // Create the unsolved block. - prevHash := g.tip.BlockHash() - block := wire.MsgBlock{ - Header: wire.BlockHeader{ - Version: 1, - PrevBlock: prevHash, - MerkleRoot: calcMerkleRoot(regularTxns), - StakeRoot: calcMerkleRoot(stakeTxns), - VoteBits: 1, - FinalState: finalState, - Voters: numVotes, - FreshStake: uint8(len(ticketPurchases)), - Revocations: numTicketRevocations, - PoolSize: uint32(len(g.liveTickets)), - Bits: g.calcNextRequiredDifficulty(), - SBits: int64(ticketPrice), - Height: nextHeight, - Size: 0, // Filled in below. - Timestamp: ts, - Nonce: 0, // To be solved. - ExtraData: [32]byte{}, - StakeVersion: 0, - }, - Transactions: regularTxns, - STransactions: stakeTxns, - } - block.Header.Size = uint32(block.SerializeSize()) - - // Perform any block munging just before solving. Only recalculate the - // merkle roots and block size if they weren't manually changed by a - // munge function. - curMerkleRoot := block.Header.MerkleRoot - curStakeRoot := block.Header.StakeRoot - curSize := block.Header.Size - curNonce := block.Header.Nonce - for _, f := range mungers { - f(&block) - } - if block.Header.MerkleRoot == curMerkleRoot { - block.Header.MerkleRoot = calcMerkleRoot(block.Transactions) - } - if block.Header.StakeRoot == curStakeRoot { - block.Header.StakeRoot = calcMerkleRoot(block.STransactions) - } - if block.Header.Size == curSize { - block.Header.Size = uint32(block.SerializeSize()) - } - - // Only solve the block if the nonce wasn't manually changed by a munge - // function. - if block.Header.Nonce == curNonce && !solveBlock(&block.Header) { - panic(fmt.Sprintf("Unable to solve block at height %d", - block.Header.Height)) - } - - // Update generator state and return the block. - blockHash := block.BlockHash() - if block.Header.PrevBlock != prevHash { - // Save the orignal block this one was built from if it was - // manually changed in a munger so the code which deals with - // updating the live tickets when changing the tip has access to - // it. - g.originalParents[blockHash] = prevHash - } - g.connectLiveTickets(&blockHash, nextHeight, ticketWinners, - ticketPurchases) - g.blocks[blockHash] = &block - g.blocksByName[blockName] = &block - g.tip = &block - g.tipName = blockName - return &block -} - -// CreatePremineBlock generates the first block of the chain with the required -// premine payouts. The additional amount parameter can be used to create a -// block that is otherwise a completely valid premine block except it adds the -// extra amount to each payout and thus create a block that violates consensus. -func (g *Generator) CreatePremineBlock(blockName string, additionalAmount hcutil.Amount) *wire.MsgBlock { - coinbaseTx := wire.NewMsgTx() - coinbaseTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, - wire.MaxPrevOutIndex, wire.TxTreeRegular), - Sequence: wire.MaxTxInSequenceNum, - ValueIn: 0, // Updated below. - BlockHeight: wire.NullBlockHeight, - BlockIndex: wire.NullBlockIndex, - SignatureScript: coinbaseSigScript, - }) - - // Add each required output and tally the total payouts for the coinbase - // in order to set the input value appropriately. - var totalSubsidy hcutil.Amount - for _, payout := range g.params.BlockOneLedger { - payoutAddr, err := hcutil.DecodeAddress(payout.Address) - if err != nil { - panic(err) - } - pkScript, err := txscript.PayToAddrScript(payoutAddr) - if err != nil { - panic(err) - } - coinbaseTx.AddTxOut(&wire.TxOut{ - Value: payout.Amount + int64(additionalAmount), - Version: 0, - PkScript: pkScript, - }) - - totalSubsidy += hcutil.Amount(payout.Amount) - } - coinbaseTx.TxIn[0].ValueIn = int64(totalSubsidy) - - // Generate the block with the specially created regular transactions. - return g.NextBlock(blockName, nil, nil, func(b *wire.MsgBlock) { - b.Transactions = []*wire.MsgTx{coinbaseTx} - }) -} - -// UpdateBlockState manually updates the generator state to remove all internal -// map references to a block via its old hash and insert new ones for the new -// block hash. This is useful if the test code has to manually change a block -// after 'NextBlock' has returned. -func (g *Generator) UpdateBlockState(oldBlockName string, oldBlockHash chainhash.Hash, newBlockName string, newBlock *wire.MsgBlock) { - // Remove existing entries. - wonTickets := g.wonTickets[oldBlockHash] - delete(g.blocks, oldBlockHash) - delete(g.blocksByName, oldBlockName) - delete(g.wonTickets, oldBlockHash) - - // Add new entries. - newBlockHash := newBlock.BlockHash() - g.blocks[newBlockHash] = newBlock - g.blocksByName[newBlockName] = newBlock - g.wonTickets[newBlockHash] = wonTickets -} - -// OldestCoinbaseOuts removes the oldest set of coinbase proof-of-work outputs -// that was previously saved to the generator and returns the set as a slice. -func (g *Generator) OldestCoinbaseOuts() []SpendableOut { - outs := g.spendableOuts[0] - g.spendableOuts = g.spendableOuts[1:] - return outs -} - -// NumSpendableCoinbaseOuts returns the number of proof-of-work outputs that -// were previously saved to the generated but have not yet been collected. -func (g *Generator) NumSpendableCoinbaseOuts() int { - return len(g.spendableOuts) -} - -// saveCoinbaseOuts adds the proof-of-work outputs of the coinbase tx in the -// passed block to the list of spendable outputs. -func (g *Generator) saveCoinbaseOuts(b *wire.MsgBlock) { - g.spendableOuts = append(g.spendableOuts, []SpendableOut{ - MakeSpendableOut(b, 0, 2), - MakeSpendableOut(b, 0, 3), - MakeSpendableOut(b, 0, 4), - MakeSpendableOut(b, 0, 5), - MakeSpendableOut(b, 0, 6), - MakeSpendableOut(b, 0, 7), - }) - g.prevCollectedHash = b.BlockHash() -} - -// SaveTipCoinbaseOuts adds the proof-of-work outputs of the coinbase tx in the -// current tip block to the list of spendable outputs. -func (g *Generator) SaveTipCoinbaseOuts() { - g.saveCoinbaseOuts(g.tip) -} - -// SaveSpendableCoinbaseOuts adds all proof-of-work coinbase outputs starting -// from the block after the last block that had its coinbase outputs collected -// and ending at the current tip. This is useful to batch the collection of the -// outputs once the tests reach a stable point so they don't have to manually -// add them for the right tests which will ultimately end up being the best -// chain. -func (g *Generator) SaveSpendableCoinbaseOuts() { - // Loop through the ancestors of the current tip until the - // reaching the block that has already had the coinbase outputs - // collected. - var collectBlocks []*wire.MsgBlock - for b := g.tip; b != nil; b = g.blocks[b.Header.PrevBlock] { - if b.BlockHash() == g.prevCollectedHash { - break - } - collectBlocks = append(collectBlocks, b) - } - for i := range collectBlocks { - g.saveCoinbaseOuts(collectBlocks[len(collectBlocks)-1-i]) - } -} - -// AssertTipHeight panics if the current tip block associated with the generator -// does not have the specified height. -func (g *Generator) AssertTipHeight(expected uint32) { - height := g.tip.Header.Height - if height != expected { - panic(fmt.Sprintf("height for block %q is %d instead of "+ - "expected %d", g.tipName, height, expected)) - } -} - -// AssertScriptSigOpsCount panics if the provided script does not have the -// specified number of signature operations. -func (g *Generator) AssertScriptSigOpsCount(script []byte, expected int) { - numSigOps := txscript.GetSigOpCount(script) - if numSigOps != expected { - _, file, line, _ := runtime.Caller(1) - panic(fmt.Sprintf("assertion failed at %s:%d: generated number "+ - "of sigops for script is %d instead of expected %d", - file, line, numSigOps, expected)) - } -} - -// countBlockSigOps returns the number of legacy signature operations in the -// scripts in the passed block. -func countBlockSigOps(block *wire.MsgBlock) int { - totalSigOps := 0 - for _, tx := range block.Transactions { - for _, txIn := range tx.TxIn { - numSigOps := txscript.GetSigOpCount(txIn.SignatureScript) - totalSigOps += numSigOps - } - for _, txOut := range tx.TxOut { - numSigOps := txscript.GetSigOpCount(txOut.PkScript) - totalSigOps += numSigOps - } - } - - return totalSigOps -} - -// AssertTipBlockSigOpsCount panics if the current tip block associated with the -// generator does not have the specified number of signature operations. -func (g *Generator) AssertTipBlockSigOpsCount(expected int) { - numSigOps := countBlockSigOps(g.tip) - if numSigOps != expected { - panic(fmt.Sprintf("generated number of sigops for block %q "+ - "(height %d) is %d instead of expected %d", g.tipName, - g.tip.Header.Height, numSigOps, expected)) - } -} - -// AssertTipBlockSize panics if the if the current tip block associated with the -// generator does not have the specified size when serialized. -func (g *Generator) AssertTipBlockSize(expected int) { - serializeSize := g.tip.SerializeSize() - if serializeSize != expected { - panic(fmt.Sprintf("block size of block %q (height %d) is %d "+ - "instead of expected %d", g.tipName, - g.tip.Header.Height, serializeSize, expected)) - } -} - -// AssertTipBlockNumTxns panics if the number of transactions in the current tip -// block associated with the generator does not match the specified value. -func (g *Generator) AssertTipBlockNumTxns(expected int) { - numTxns := len(g.tip.Transactions) - if numTxns != expected { - panic(fmt.Sprintf("number of txns in block %q (height %d) is "+ - "%d instead of expected %d", g.tipName, - g.tip.Header.Height, numTxns, expected)) - } -} - -// AssertTipBlockHash panics if the current tip block associated with the -// generator does not match the specified hash. -func (g *Generator) AssertTipBlockHash(expected chainhash.Hash) { - hash := g.tip.BlockHash() - if hash != expected { - panic(fmt.Sprintf("block hash of block %q (height %d) is %v "+ - "instead of expected %v", g.tipName, - g.tip.Header.Height, hash, expected)) - } -} - -// AssertTipBlockMerkleRoot panics if the merkle root in header of the current -// tip block associated with the generator does not match the specified hash. -func (g *Generator) AssertTipBlockMerkleRoot(expected chainhash.Hash) { - hash := g.tip.Header.MerkleRoot - if hash != expected { - panic(fmt.Sprintf("merkle root of block %q (height %d) is %v "+ - "instead of expected %v", g.tipName, - g.tip.Header.Height, hash, expected)) - } -} - -// AssertTipBlockTxOutOpReturn panics if the current tip block associated with -// the generator does not have an OP_RETURN script for the transaction output at -// the provided tx index and output index. -func (g *Generator) AssertTipBlockTxOutOpReturn(txIndex, txOutIndex uint32) { - if txIndex >= uint32(len(g.tip.Transactions)) { - panic(fmt.Sprintf("Transaction index %d in block %q "+ - "(height %d) does not exist", txIndex, g.tipName, - g.tip.Header.Height)) - } - - tx := g.tip.Transactions[txIndex] - if txOutIndex >= uint32(len(tx.TxOut)) { - panic(fmt.Sprintf("transaction index %d output %d in block %q "+ - "(height %d) does not exist", txIndex, txOutIndex, - g.tipName, g.tip.Header.Height)) - } - - txOut := tx.TxOut[txOutIndex] - if txOut.PkScript[0] != txscript.OP_RETURN { - panic(fmt.Sprintf("transaction index %d output %d in block %q "+ - "(height %d) is not an OP_RETURN", txIndex, txOutIndex, - g.tipName, g.tip.Header.Height)) - } -} - -// AssertStakeVersion panics if the current tip block associated with the -// generator does not have the specified stake version in the header. -func (g *Generator) AssertStakeVersion(expected uint32) { - stakeVersion := g.tip.Header.StakeVersion - if stakeVersion != expected { - panic(fmt.Sprintf("stake version for block %q is %d instead of "+ - "expected %d", g.tipName, stakeVersion, expected)) - } -} - -// AssertBlockVersion panics if the current tip block associated with the -// generator does not have the specified version. -func (g *Generator) AssertBlockVersion(expected int32) { - blockVersion := g.tip.Header.Version - if blockVersion != expected { - panic(fmt.Sprintf("block version for block %q is %d instead of "+ - "expected %d", g.tipName, blockVersion, expected)) - } -} +// Copyright (c) 2016-2017 The Decred developers +// Copyright (c) 2018-2020 The Hc developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package chaingen + +import ( + "bytes" + "encoding/binary" + "fmt" + "math" + "math/big" + "runtime" + "sort" + "time" + + "github.com/james-ray/hcd/blockchain" + "github.com/james-ray/hcd/chaincfg" + "github.com/james-ray/hcd/chaincfg/chainhash" + "github.com/james-ray/hcd/hcutil" + "github.com/james-ray/hcd/txscript" + "github.com/james-ray/hcd/wire" +) + +var ( + // hash256prngSeedConst is a constant derived from the hex + // representation of pi and is used in conjuction with a caller-provided + // seed when initializing the deterministic lottery prng. + hash256prngSeedConst = []byte{0x24, 0x3f, 0x6a, 0x88, 0x85, 0xa3, 0x08, + 0xd3} + + // opTrueScript is a simple public key script that contains the OP_TRUE + // opcode. It is defined here to reduce garbage creation. + opTrueScript = []byte{txscript.OP_TRUE} + + // opTrueRedeemScript is the signature script that can be used to redeem + // a p2sh output to the opTrueScript. It is defined here to reduce + // garbage creation. + opTrueRedeemScript = []byte{txscript.OP_DATA_1, txscript.OP_TRUE} + + // coinbaseSigScript is the signature script used by the tests when + // creating standard coinbase transactions. It is defined here to + // reduce garbage creation. + coinbaseSigScript = []byte{txscript.OP_0, txscript.OP_0} +) + +const ( + // voteBitYes is the specific bit that is set in the vote bits to + // indicate that the previous block is valid. + voteBitYes = 0x01 +) + +// SpendableOut represents a transaction output that is spendable along with +// additional metadata such as the block its in and how much it pays. +type SpendableOut struct { + prevOut wire.OutPoint + blockHeight uint32 + blockIndex uint32 + amount hcutil.Amount +} + +// PrevOut returns the outpoint associated with the spendable output. +func (s *SpendableOut) PrevOut() wire.OutPoint { + return s.prevOut +} + +// BlockHeight returns the block height of the block the spendable output is in. +func (s *SpendableOut) BlockHeight() uint32 { + return s.blockHeight +} + +// BlockIndex returns the offset into the block the spendable output is in. +func (s *SpendableOut) BlockIndex() uint32 { + return s.blockIndex +} + +// Amount returns the amount associated with the spendable output. +func (s *SpendableOut) Amount() hcutil.Amount { + return s.amount +} + +// MakeSpendableOutForTx returns a spendable output for the given transaction +// block height, transaction index within the block, and transaction output +// index within the transaction. +func MakeSpendableOutForTx(tx *wire.MsgTx, blockHeight, txIndex, txOutIndex uint32) SpendableOut { + return SpendableOut{ + prevOut: wire.OutPoint{ + Hash: *tx.CachedTxHash(), + Index: txOutIndex, + Tree: wire.TxTreeRegular, + }, + blockHeight: blockHeight, + blockIndex: txIndex, + amount: hcutil.Amount(tx.TxOut[txOutIndex].Value), + } +} + +// MakeSpendableOut returns a spendable output for the given block, transaction +// index within the block, and transaction output index within the transaction. +func MakeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) SpendableOut { + tx := block.Transactions[txIndex] + return MakeSpendableOutForTx(tx, block.Header.Height, txIndex, txOutIndex) +} + +// stakeTicket represents a transaction that is an sstx along with the height of +// the block it was mined in and the its index within that block. +type stakeTicket struct { + tx *wire.MsgTx + blockHeight uint32 + blockIndex uint32 +} + +// stakeTicketSorter implements sort.Interface to allow a slice of stake tickets +// to be sorted. +type stakeTicketSorter []*stakeTicket + +// Len returns the number of stake tickets in the slice. It is part of the +// sort.Interface implementation. +func (t stakeTicketSorter) Len() int { return len(t) } + +// Swap swaps the stake tickets at the passed indices. It is part of the +// sort.Interface implementation. +func (t stakeTicketSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] } + +// Less returns whether the stake ticket with index i should sort before the +// stake ticket with index j. It is part of the sort.Interface implementation. +func (t stakeTicketSorter) Less(i, j int) bool { + iHash := t[i].tx.CachedTxHash()[:] + jHash := t[j].tx.CachedTxHash()[:] + return bytes.Compare(iHash, jHash) < 0 +} + +// Generator houses state used to ease the process of generating test blocks +// that build from one another along with housing other useful things such as +// available spendable outputs and generic payment scripts used throughout the +// tests. +type Generator struct { + params *chaincfg.Params + tip *wire.MsgBlock + tipName string + blocks map[chainhash.Hash]*wire.MsgBlock + blocksByName map[string]*wire.MsgBlock + p2shOpTrueAddr hcutil.Address + p2shOpTrueScript []byte + + // Used for tracking spendable coinbase outputs. + spendableOuts [][]SpendableOut + prevCollectedHash chainhash.Hash + + // Used for tracking the live ticket pool. + originalParents map[chainhash.Hash]chainhash.Hash + immatureTickets []*stakeTicket + liveTickets []*stakeTicket + wonTickets map[chainhash.Hash][]*stakeTicket + expiredTickets []*stakeTicket +} + +// MakeGenerator returns a generator instance initialized with the genesis block +// as the tip as well as a cached generic pay-to-script-hash script for OP_TRUE. +func MakeGenerator(params *chaincfg.Params) (Generator, error) { + // Generate a generic pay-to-script-hash script that is a simple + // OP_TRUE. This allows the tests to avoid needing to generate and + // track actual public keys and signatures. + p2shOpTrueAddr, err := hcutil.NewAddressScriptHash(opTrueScript, params) + if err != nil { + return Generator{}, err + } + p2shOpTrueScript, err := txscript.PayToAddrScript(p2shOpTrueAddr) + if err != nil { + return Generator{}, err + } + + genesis := params.GenesisBlock + genesisHash := genesis.BlockHash() + return Generator{ + params: params, + tip: genesis, + tipName: "genesis", + blocks: map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis}, + blocksByName: map[string]*wire.MsgBlock{"genesis": genesis}, + p2shOpTrueAddr: p2shOpTrueAddr, + p2shOpTrueScript: p2shOpTrueScript, + originalParents: make(map[chainhash.Hash]chainhash.Hash), + wonTickets: make(map[chainhash.Hash][]*stakeTicket), + }, nil +} + +// Params returns the chain params associated with the generator instance. +func (g *Generator) Params() *chaincfg.Params { + return g.params +} + +// Tip returns the current tip block of the generator instance. +func (g *Generator) Tip() *wire.MsgBlock { + return g.tip +} + +// TipName returns the name of the current tip block of the generator instance. +func (g *Generator) TipName() string { + return g.tipName +} + +// BlockByName returns the block associated with the provided block name. It +// will panic if the specified block name does not exist. +func (g *Generator) BlockByName(blockName string) *wire.MsgBlock { + block, ok := g.blocksByName[blockName] + if !ok { + panic(fmt.Sprintf("block name %s does not exist", blockName)) + } + return block +} + +// BlockByHash returns the block associated with the provided block hash. It +// will panic if the specified block hash does not exist. +func (g *Generator) BlockByHash(hash *chainhash.Hash) *wire.MsgBlock { + block, ok := g.blocks[*hash] + if !ok { + panic(fmt.Sprintf("block with hash %s does not exist", hash)) + } + return block +} + +// opReturnScript returns a provably-pruneable OP_RETURN script with the +// provided data. +func opReturnScript(data []byte) []byte { + builder := txscript.NewScriptBuilder() + script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() + if err != nil { + panic(err) + } + return script +} + +// UniqueOpReturnScript returns a standard provably-pruneable OP_RETURN script +// with a random uint64 encoded as the data. +func UniqueOpReturnScript() []byte { + rand, err := wire.RandomUint64() + if err != nil { + panic(err) + } + + data := make([]byte, 8, 8) + binary.LittleEndian.PutUint64(data[0:8], rand) + return opReturnScript(data) +} + +// calcFullSubsidy returns the full block subsidy for the given block height. +// +// NOTE: This and the other subsidy calculation funcs intentionally are not +// using the blockchain code since the intent is to be able to generate known +// good tests which exercise that code, so it wouldn't make sense to use the +// same code to generate them. +//A(n) = (a1+(n-1)d)q^(n-1) +//S(n) = a1(1-q^n)/(1-q) + d[q(1-q^(n-1))/((1-q)^2) - (n-1)q^n/(1-q)] +//A(n) = A(n-1) *q + d*q^(n-1) + +func (g *Generator) calcFullSubsidy(blockHeight uint32) hcutil.Amount { + iterations := int64(blockHeight) / g.params.SubsidyReductionInterval + subsidy := g.params.BaseSubsidy + var q float64 = float64(g.params.MulSubsidy) / float64(g.params.DivSubsidy) + var temp float64 = 0.0 + + if iterations < 1682 { + temp = float64(g.params.BaseSubsidy) * (1.0 - float64(iterations)*59363.0/100000000.0) * math.Pow(q, float64(iterations)) + } else { //after 99 years + temp = 100000000.0 / float64(g.params.SubsidyReductionInterval) * math.Pow(0.1, float64(float64(iterations)-1681.0)) + } + subsidy = int64(temp) + return hcutil.Amount(subsidy) +} + +// calcPoWSubsidy returns the proof-of-work subsidy portion from a given full +// subsidy, block height, and number of votes that will be included in the +// block. +// +// NOTE: This and the other subsidy calculation funcs intentionally are not +// using the blockchain code since the intent is to be able to generate known +// good tests which exercise that code, so it wouldn't make sense to use the +// same code to generate them. +func (g *Generator) calcPoWSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount { + powProportion := hcutil.Amount(g.params.WorkRewardProportion) + totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) + powSubsidy := (fullSubsidy * powProportion) / totalProportions + if int64(blockHeight) < g.params.StakeValidationHeight { + return powSubsidy + } + + // Reduce the subsidy according to the number of votes. + ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock) + return (powSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock +} + +// calcPoSSubsidy returns the proof-of-stake subsidy portion for a given block +// height being voted on. +// +// NOTE: This and the other subsidy calculation funcs intentionally are not +// using the blockchain code since the intent is to be able to generate known +// good tests which exercise that code, so it wouldn't make sense to use the +// same code to generate them. +func (g *Generator) calcPoSSubsidy(heightVotedOn uint32) hcutil.Amount { + if int64(heightVotedOn+1) < g.params.StakeValidationHeight { + return 0 + } + + fullSubsidy := g.calcFullSubsidy(heightVotedOn) + posProportion := hcutil.Amount(g.params.StakeRewardProportion) + totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) + return (fullSubsidy * posProportion) / totalProportions +} + +// calcDevSubsidy returns the dev org subsidy portion from a given full subsidy. +// +// NOTE: This and the other subsidy calculation funcs intentionally are not +// using the blockchain code since the intent is to be able to generate known +// good tests which exercise that code, so it wouldn't make sense to use the +// same code to generate them. +func (g *Generator) calcDevSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount { + devProportion := hcutil.Amount(g.params.BlockTaxProportion) + totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions()) + devSubsidy := (fullSubsidy * devProportion) / totalProportions + if int64(blockHeight) < g.params.StakeValidationHeight { + return devSubsidy + } + + // Reduce the subsidy according to the number of votes. + ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock) + return (devSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock +} + +// standardCoinbaseOpReturnScript returns a standard script suitable for use as +// the second output of a standard coinbase transaction of a new block. In +// particular, the serialized data used with the OP_RETURN starts with the block +// height and is followed by 32 bytes which are treated as 4 uint64 extra +// nonces. This implementation puts a cryptographically random value into the +// final extra nonce position. The actual format of the data after the block +// height is not defined however this effectivley mirrors the actual mining code +// at the time it was written. +func standardCoinbaseOpReturnScript(blockHeight uint32) []byte { + rand, err := wire.RandomUint64() + if err != nil { + panic(err) + } + + data := make([]byte, 36, 36) + binary.LittleEndian.PutUint32(data[0:4], blockHeight) + binary.LittleEndian.PutUint64(data[28:36], rand) + return opReturnScript(data) +} + +// addCoinbaseTxOutputs adds the following outputs to the provided transaction +// which is assumed to be a coinbase transaction: +// - First output pays the development subsidy portion to the dev org +// - Second output is a standard provably prunable data-only coinbase output +// - Third and subsequent outputs pay the pow subsidy portion to the generic +// OP_TRUE p2sh script hash +func (g *Generator) addCoinbaseTxOutputs(tx *wire.MsgTx, blockHeight uint32, devSubsidy, powSubsidy hcutil.Amount) { + // First output is the developer subsidy. + tx.AddTxOut(&wire.TxOut{ + Value: int64(devSubsidy), + Version: g.params.OrganizationPkScriptVersion, + PkScript: g.params.OrganizationPkScript, + }) + + // Second output is a provably prunable data-only output that is used + // to ensure the coinbase is unique. + tx.AddTxOut(wire.NewTxOut(0, standardCoinbaseOpReturnScript(blockHeight))) + + // Final outputs are the proof-of-work subsidy split into more than one + // output. These are in turn used througout the tests as inputs to + // other transactions such as ticket purchases and additional spend + // transactions. + const numPoWOutputs = 6 + amount := powSubsidy / numPoWOutputs + for i := 0; i < numPoWOutputs; i++ { + if i == numPoWOutputs-1 { + amount = powSubsidy - amount*(numPoWOutputs-1) + } + tx.AddTxOut(wire.NewTxOut(int64(amount), g.p2shOpTrueScript)) + } +} + +// CreateCoinbaseTx returns a coinbase transaction paying an appropriate +// subsidy based on the passed block height and number of votes to the dev org +// and proof-of-work miner. +// +// See the addCoinbaseTxOutputs documentation for a breakdown of the outputs +// the transaction contains. +func (g *Generator) CreateCoinbaseTx(blockHeight uint32, numVotes uint16) *wire.MsgTx { + // Calculate the subsidy proportions based on the block height and the + // number of votes the block will include. + fullSubsidy := g.calcFullSubsidy(blockHeight) + devSubsidy := g.calcDevSubsidy(fullSubsidy, blockHeight, numVotes) + powSubsidy := g.calcPoWSubsidy(fullSubsidy, blockHeight, numVotes) + + tx := wire.NewMsgTx() + tx.AddTxIn(&wire.TxIn{ + // Coinbase transactions have no inputs, so previous outpoint is + // zero hash and max index. + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex, wire.TxTreeRegular), + Sequence: wire.MaxTxInSequenceNum, + ValueIn: int64(devSubsidy + powSubsidy), + BlockHeight: wire.NullBlockHeight, + BlockIndex: wire.NullBlockIndex, + SignatureScript: coinbaseSigScript, + }) + + g.addCoinbaseTxOutputs(tx, blockHeight, devSubsidy, powSubsidy) + + return tx +} + +// purchaseCommitmentScript returns a standard provably-pruneable OP_RETURN +// commitment script suitable for use in a ticket purchase tx (sstx) using the +// provided target address, amount, and fee limits. +func purchaseCommitmentScript(addr hcutil.Address, amount, voteFeeLimit, revocationFeeLimit hcutil.Amount) []byte { + // The limits are defined in terms of the closest base 2 exponent and + // a bit that must be set to specify the limit is to be applied. The + // vote fee exponent is in the bottom 8 bits, while the revocation fee + // exponent is in the upper 8 bits. + limits := uint16(0) + if voteFeeLimit != 0 { + exp := uint16(math.Ceil(math.Log2(float64(voteFeeLimit)))) + limits |= (exp | 0x40) + } + if revocationFeeLimit != 0 { + exp := uint16(math.Ceil(math.Log2(float64(revocationFeeLimit)))) + limits |= ((exp | 0x40) << 8) + } + + // The data consists of the 20-byte raw script address for the given + // address, 8 bytes for the amount to commit to (with the upper bit flag + // set to indicate a pay-to-script-hash address), and 2 bytes for the + // fee limits. + var data [30]byte + copy(data[:], addr.ScriptAddress()) + binary.LittleEndian.PutUint64(data[20:], uint64(amount)) + data[27] |= 1 << 7 + binary.LittleEndian.PutUint16(data[28:], limits) + script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). + AddData(data[:]).Script() + if err != nil { + panic(err) + } + return script +} + +// createTicketPurchaseTx creates a new transaction that spends the provided +// output to purchase a stake submission ticket (sstx) at the given ticket +// price. Both the ticket and the change will go to a p2sh script that is +// composed with a single OP_TRUE. +// +// The transaction consists of the following outputs: +// - First output is an OP_SSTX followed by the OP_TRUE p2sh script hash +// - Second output is an OP_RETURN followed by the commitment script +// - Third output is an OP_SSTXCHANGE followed by the OP_TRUE p2sh script hash +func (g *Generator) createTicketPurchaseTx(spend *SpendableOut, ticketPrice, fee hcutil.Amount) *wire.MsgTx { + // The first output is the voting rights address. This impl uses the + // standard pay-to-script-hash to an OP_TRUE. + pkScript, err := txscript.PayToSStx(g.p2shOpTrueAddr) + if err != nil { + panic(err) + } + + // Generate the commitment script. + commitScript := purchaseCommitmentScript(g.p2shOpTrueAddr, + ticketPrice+fee, 0, ticketPrice) + + // Calculate change and generate script to deliver it. + change := spend.amount - ticketPrice - fee + changeScript, err := txscript.PayToSStxChange(g.p2shOpTrueAddr) + if err != nil { + panic(err) + } + + // Generate and return the transaction spending from the provided + // spendable output with the previously described outputs. + tx := wire.NewMsgTx() + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: spend.prevOut, + Sequence: wire.MaxTxInSequenceNum, + ValueIn: int64(spend.amount), + BlockHeight: spend.blockHeight, + BlockIndex: spend.blockIndex, + SignatureScript: opTrueRedeemScript, + }) + tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript)) + tx.AddTxOut(wire.NewTxOut(0, commitScript)) + tx.AddTxOut(wire.NewTxOut(int64(change), changeScript)) + return tx +} + +// isTicketPurchaseTx returns whether or not the passed transaction is a stake +// ticket purchase. +// +// NOTE: Like many other functions in this test code, this function +// intentionally does not use the blockchain/stake package code since the intent +// is to be able to generate known good tests which exercise that code, so it +// wouldn't make sense to use the same code to generate them. It must also be +// noted that this function is NOT robust. It is the minimum necessary needed +// by the testing framework. +func isTicketPurchaseTx(tx *wire.MsgTx) bool { + if len(tx.TxOut) == 0 { + return false + } + txOut := tx.TxOut[0] + scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) + return scriptClass == txscript.StakeSubmissionTy +} + +// isVoteTx returns whether or not the passed tx is a stake vote (ssgen). +// +// NOTE: Like many other functions in this test code, this function +// intentionally does not use the blockchain/stake package code since the intent +// is to be able to generate known good tests which exercise that code, so it +// wouldn't make sense to use the same code to generate them. It must also be +// noted that this function is NOT robust. It is the minimum necessary needed +// by the testing framework. +func isVoteTx(tx *wire.MsgTx) bool { + if len(tx.TxOut) < 3 { + return false + } + txOut := tx.TxOut[2] + scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) + return scriptClass == txscript.StakeGenTy +} + +// isRevocationTx returns whether or not the passed tx is a stake ticket +// revocation (ssrtx). +// +// NOTE: Like many other functions in this test code, this function +// intentionally does not use the blockchain/stake package code since the intent +// is to be able to generate known good tests which exercise that code, so it +// wouldn't make sense to use the same code to generate them. It must also be +// noted that this function is NOT robust. It is the minimum necessary needed +// by the testing framework. +func isRevocationTx(tx *wire.MsgTx) bool { + if len(tx.TxOut) == 0 { + return false + } + txOut := tx.TxOut[0] + scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) + return scriptClass == txscript.StakeRevocationTy +} + +// voteBlockScript returns a standard provably-pruneable OP_RETURN script +// suitable for use in a vote tx (ssgen) given the block to vote on. +func voteBlockScript(parentBlock *wire.MsgBlock) []byte { + var data [36]byte + parentHash := parentBlock.BlockHash() + copy(data[:], parentHash[:]) + binary.LittleEndian.PutUint32(data[32:], parentBlock.Header.Height) + script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). + AddData(data[:]).Script() + if err != nil { + panic(err) + } + return script +} + +// voteBitsScript returns a standard provably-pruneable OP_RETURN script +// suitable for use in a vote tx (ssgen) with the appropriate vote bits set +// depending on the provided params. +func voteBitsScript(bits uint16, voteVersion uint32) []byte { + data := make([]byte, 6) + binary.LittleEndian.PutUint16(data[:], bits) + binary.LittleEndian.PutUint32(data[2:], voteVersion) + if voteVersion == 0 { + data = data[:2] + } + script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN). + AddData(data[:]).Script() + if err != nil { + panic(err) + } + return script +} + +// createVoteTx returns a new transaction (ssgen) paying an appropriate subsidy +// for the given block height (and the number of votes per block) as well as the +// original commitments. +// +// The transaction consists of the following outputs: +// - First output is an OP_RETURN followed by the block hash and height +// - Second output is an OP_RETURN followed by the vote bits +// - Third and subsequent outputs are the payouts according to the ticket +// commitments and the appropriate proportion of the vote subsidy. +func (g *Generator) createVoteTx(parentBlock *wire.MsgBlock, ticket *stakeTicket) *wire.MsgTx { + // Calculate the proof-of-stake subsidy proportion based on the block + // height. + posSubsidy := g.calcPoSSubsidy(parentBlock.Header.Height) + voteSubsidy := posSubsidy / hcutil.Amount(g.params.TicketsPerBlock) + ticketPrice := hcutil.Amount(ticket.tx.TxOut[0].Value) + + // The first output is the block (hash and height) the vote is for. + blockScript := voteBlockScript(parentBlock) + + // The second output is the vote bits. + voteScript := voteBitsScript(voteBitYes, 0) + + // The third and subsequent outputs pay the original commitment amounts + // along with the appropriate portion of the vote subsidy. This impl + // uses the standard pay-to-script-hash to an OP_TRUE. + stakeGenScript, err := txscript.PayToSSGen(g.p2shOpTrueAddr) + if err != nil { + panic(err) + } + + // Generate and return the transaction with the proof-of-stake subsidy + // coinbase and spending from the provided ticket along with the + // previously described outputs. + ticketHash := ticket.tx.CachedTxHash() + tx := wire.NewMsgTx() + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex, wire.TxTreeRegular), + Sequence: wire.MaxTxInSequenceNum, + ValueIn: int64(voteSubsidy), + BlockHeight: wire.NullBlockHeight, + BlockIndex: wire.NullBlockIndex, + SignatureScript: g.params.StakeBaseSigScript, + }) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *wire.NewOutPoint(ticketHash, 0, + wire.TxTreeStake), + Sequence: wire.MaxTxInSequenceNum, + ValueIn: int64(ticketPrice), + BlockHeight: ticket.blockHeight, + BlockIndex: ticket.blockIndex, + SignatureScript: opTrueRedeemScript, + }) + tx.AddTxOut(wire.NewTxOut(0, blockScript)) + tx.AddTxOut(wire.NewTxOut(0, voteScript)) + tx.AddTxOut(wire.NewTxOut(int64(voteSubsidy+ticketPrice), stakeGenScript)) + return tx +} + +// ancestorBlock returns the ancestor block at the provided height by following +// the chain backwards from the given block. The returned block will be nil +// when a height is requested that is after the height of the passed block. +// Also, a callback can optionally be provided that is invoked with each block +// as it traverses. +func (g *Generator) ancestorBlock(block *wire.MsgBlock, height uint32, f func(*wire.MsgBlock)) *wire.MsgBlock { + // Nothing to do if the requested height is outside of the valid + // range. + if block == nil || height > block.Header.Height { + return nil + } + + // Iterate backwards until the requested height is reached. + for block != nil && block.Header.Height > height { + block = g.blocks[block.Header.PrevBlock] + if f != nil && block != nil { + f(block) + } + } + + return block +} + +// mergeDifficulty takes an original stake difficulty and two new, scaled +// stake difficulties, merges the new difficulties, and outputs a new +// merged stake difficulty. +func mergeDifficulty(oldDiff int64, newDiff1 int64, newDiff2 int64) int64 { + newDiff1Big := big.NewInt(newDiff1) + newDiff2Big := big.NewInt(newDiff2) + newDiff2Big.Lsh(newDiff2Big, 32) + + oldDiffBig := big.NewInt(oldDiff) + oldDiffBigLSH := big.NewInt(oldDiff) + oldDiffBigLSH.Lsh(oldDiffBig, 32) + + newDiff1Big.Div(oldDiffBigLSH, newDiff1Big) + newDiff2Big.Div(newDiff2Big, oldDiffBig) + + // Combine the two changes in difficulty. + summedChange := big.NewInt(0) + summedChange.Set(newDiff2Big) + summedChange.Lsh(summedChange, 32) + summedChange.Div(summedChange, newDiff1Big) + summedChange.Mul(summedChange, oldDiffBig) + summedChange.Rsh(summedChange, 32) + + return summedChange.Int64() +} + +// limitRetarget clamps the passed new difficulty to the old one adjusted by the +// factor specified in the chain parameters. This ensures the difficulty can +// only move up or down by a limited amount. +func (g *Generator) limitRetarget(oldDiff, newDiff int64) int64 { + maxRetarget := g.params.RetargetAdjustmentFactor + switch { + case newDiff == 0: + fallthrough + case (oldDiff / newDiff) > (maxRetarget - 1): + return oldDiff / maxRetarget + case (newDiff / oldDiff) > (maxRetarget - 1): + return oldDiff * maxRetarget + } + + return newDiff +} + +// calcNextRequiredDifficulty returns the required proof-of-work difficulty for +// the block after the current tip block the generator is associated with. +// +// An overview of the algorithm is as follows: +// 1. Use the proof-of-work limit for all blocks before the first retarget +// window +// 2. Use the previous block's difficulty if the next block is not at a retarget +// interval +// 3. Calculate the ideal retarget difficulty for each window based on the +// actual timespan of the window versus the target timespan and exponentially +// weight each difficulty such that the most recent window has the highest +// weight +// 4. Calculate the final retarget difficulty based on the exponential weighted +// average and ensure it is limited to the max retarget adjustment factor +func (g *Generator) calcNextRequiredDifficulty() uint32 { + // Target difficulty before the first retarget interval is the pow + // limit. + nextHeight := g.tip.Header.Height + 1 + windowSize := g.params.WorkDiffWindowSize + if int64(nextHeight) < windowSize { + return g.params.PowLimitBits + } + + // Return the previous block's difficulty requirements if the next block + // is not at a difficulty retarget interval. + curDiff := int64(g.tip.Header.Bits) + if int64(nextHeight)%windowSize != 0 { + return uint32(curDiff) + } + + // Calculate the ideal retarget difficulty for each window based on the + // actual time between blocks versus the target time and exponentially + // weight them. + adjustedTimespan := big.NewInt(0) + tempBig := big.NewInt(0) + weightedTimespanSum, weightSum := big.NewInt(0), big.NewInt(0) + targetTimespan := int64(g.params.TargetTimespan) + targetTimespanBig := big.NewInt(targetTimespan) + numWindows := g.params.WorkDiffWindows + weightAlpha := g.params.WorkDiffAlpha + block := g.tip + finalWindowTime := block.Header.Timestamp.UnixNano() + for i := int64(0); i < numWindows; i++ { + // Get the timestamp of the block at the start of the window and + // calculate the actual timespan accordingly. Use the target + // timespan if there are not yet enough blocks left to cover the + // window. + actualTimespan := targetTimespan + if int64(block.Header.Height) > windowSize { + for j := int64(0); j < windowSize; j++ { + block = g.blocks[block.Header.PrevBlock] + } + startWindowTime := block.Header.Timestamp.UnixNano() + actualTimespan = finalWindowTime - startWindowTime + + // Set final window time for the next window. + finalWindowTime = startWindowTime + } + + // Calculate the ideal retarget difficulty for the window based + // on the actual timespan and weight it exponentially by + // multiplying it by 2^(window_number) such that the most recent + // window receives the most weight. + // + // Also, since integer division is being used, shift up the + // number of new tickets 32 bits to avoid losing precision. + // + // windowWeightShift = ((numWindows - i) * weightAlpha) + // adjustedTimespan = (actualTimespan << 32) / targetTimespan + // weightedTimespanSum += adjustedTimespan << windowWeightShift + // weightSum += 1 << windowWeightShift + windowWeightShift := uint((numWindows - i) * weightAlpha) + adjustedTimespan.SetInt64(actualTimespan) + adjustedTimespan.Lsh(adjustedTimespan, 32) + adjustedTimespan.Div(adjustedTimespan, targetTimespanBig) + adjustedTimespan.Lsh(adjustedTimespan, windowWeightShift) + weightedTimespanSum.Add(weightedTimespanSum, adjustedTimespan) + weight := tempBig.SetInt64(1) + weight.Lsh(weight, windowWeightShift) + weightSum.Add(weightSum, weight) + } + + // Calculate the retarget difficulty based on the exponential weigthed + // average and shift the result back down 32 bits to account for the + // previous shift up in order to avoid losing precision. Then, limit it + // to the maximum allowed retarget adjustment factor. + // + // nextDiff = (weightedTimespanSum/weightSum * curDiff) >> 32 + curDiffBig := tempBig.SetInt64(curDiff) + weightedTimespanSum.Div(weightedTimespanSum, weightSum) + weightedTimespanSum.Mul(weightedTimespanSum, curDiffBig) + weightedTimespanSum.Rsh(weightedTimespanSum, 32) + nextDiff := weightedTimespanSum.Int64() + nextDiff = g.limitRetarget(curDiff, nextDiff) + + if nextDiff > int64(g.params.PowLimitBits) { + return g.params.PowLimitBits + } + return uint32(nextDiff) +} + +// calcNextRequiredStakeDifficulty returns the required stake difficulty (aka +// ticket price) for the block after the current tip block the generator is +// associated with. +// +// An overview of the algorithm is as follows: +// 1. Use the minimum value for any blocks before any tickets could have +// possibly been purchased due to coinbase maturity requirements +// 2. Return 0 if the current tip block stake difficulty is 0. This is a +// safety check against a condition that should never actually happen. +// 3. Use the previous block's difficulty if the next block is not at a retarget +// interval +// 4. Calculate the ideal retarget difficulty for each window based on the +// actual pool size in the window versus the target pool size skewed by a +// constant factor to weight the ticket pool size instead of the tickets per +// block and exponentially weight each difficulty such that the most recent +// window has the highest weight +// 5. Calculate the pool size retarget difficulty based on the exponential +// weighted average and ensure it is limited to the max retarget adjustment +// factor -- This is the first metric used to calculate the final difficulty +// 6. Calculate the ideal retarget difficulty for each window based on the +// actual new tickets in the window versus the target new tickets per window +// and exponentially weight each difficulty such that the most recent window +// has the highest weight +// 7. Calculate the tickets per window retarget difficulty based on the +// exponential weighted average and ensure it is limited to the max retarget +// adjustment factor +// 8. Calculate the final difficulty by averaging the pool size retarget +// difficulty from #5 and the tickets per window retarget difficulty from #7 +// using scaled multiplication and ensure it is limited to the max retarget +// adjustment factor +// +// NOTE: In order to simplify the test code, this implementation does not use +// big integers so it will NOT match the actual consensus code for really big +// numbers. However, the parameters on simnet and the pool sizes used in these +// tests are low enough that this is not an issue for the tests. Anyone looking +// at this code should NOT use it for mainnet calculations as is since it will +// not always yield the correct results. +func (g *Generator) calcNextRequiredStakeDifficulty() int64 { + // Stake difficulty before any tickets could possibly be purchased is + // the minimum value. + nextHeight := g.tip.Header.Height + 1 + stakeDiffStartHeight := uint32(g.params.CoinbaseMaturity) + 1 + if nextHeight < stakeDiffStartHeight { + return g.params.MinimumStakeDiff + } + + // Return 0 if the current difficulty is already zero since any scaling + // of 0 is still 0. This should never really happen since there is a + // minimum stake difficulty, but the consensus code checks the condition + // just in case, so follow suit here. + curDiff := g.tip.Header.SBits + if curDiff == 0 { + return 0 + } + + // Return the previous block's difficulty requirements if the next block + // is not at a difficulty retarget interval. + windowSize := g.params.StakeDiffWindowSize + if int64(nextHeight)%windowSize != 0 { + return curDiff + } + + // -------------------------------- + // Ideal pool size retarget metric. + // -------------------------------- + + // Calculate the ideal retarget difficulty for each window based on the + // actual pool size in the window versus the target pool size and + // exponentially weight them. + var weightedPoolSizeSum, weightSum uint64 + ticketsPerBlock := int64(g.params.TicketsPerBlock) + targetPoolSize := ticketsPerBlock * int64(g.params.TicketPoolSize) + numWindows := g.params.StakeDiffWindows + weightAlpha := g.params.StakeDiffAlpha + block := g.tip + for i := int64(0); i < numWindows; i++ { + // Get the pool size for the block at the start of the window. + // Use zero if there are not yet enough blocks left to cover the + // window. + prevRetargetHeight := nextHeight - uint32(windowSize*(i+1)) + windowPoolSize := int64(0) + block = g.ancestorBlock(block, prevRetargetHeight, nil) + if block != nil { + windowPoolSize = int64(block.Header.PoolSize) + } + + // Skew the pool size by the constant weight factor specified in + // the chain parameters (which is typically the max adjustment + // factor) in order to help weight the ticket pool size versus + // tickets per block. Also, ensure the skewed pool size is a + // minimum of 1. + skewedPoolSize := targetPoolSize + (windowPoolSize- + targetPoolSize)*int64(g.params.TicketPoolSizeWeight) + if skewedPoolSize <= 0 { + skewedPoolSize = 1 + } + + // Calculate the ideal retarget difficulty for the window based + // on the skewed pool size and weight it exponentially by + // multiplying it by 2^(window_number) such that the most recent + // window receives the most weight. + // + // Also, since integer division is being used, shift up the + // number of new tickets 32 bits to avoid losing precision. + // + // NOTE: The real algorithm uses big ints, but these purpose + // built tests won't be using large enough values to overflow, + // so just use uint64s. + adjusted := (skewedPoolSize << 32) / targetPoolSize + adjusted = adjusted << uint64((numWindows-i)*weightAlpha) + weightedPoolSizeSum += uint64(adjusted) + weightSum += 1 << uint64((numWindows-i)*weightAlpha) + } + + // Calculate the pool size retarget difficulty based on the exponential + // weigthed average and shift the result back down 32 bits to account + // for the previous shift up in order to avoid losing precision. Then, + // limit it to the maximum allowed retarget adjustment factor. + // + // This is the first metric used in the final calculated difficulty. + nextPoolSizeDiff := (int64(weightedPoolSizeSum/weightSum) * curDiff) >> 32 + nextPoolSizeDiff = g.limitRetarget(curDiff, nextPoolSizeDiff) + + // ----------------------------------------- + // Ideal tickets per window retarget metric. + // ----------------------------------------- + + // Calculate the ideal retarget difficulty for each window based on the + // actual number of new tickets in the window versus the target tickets + // per window and exponentially weight them. + var weightedTicketsSum uint64 + targetTicketsPerWindow := ticketsPerBlock * windowSize + block = g.tip + for i := int64(0); i < numWindows; i++ { + // Since the difficulty for the next block after the current tip + // is being calculated and there is no such block yet, the sum + // of all new tickets in the first window needs to start with + // the number of new tickets in the tip block. + var windowNewTickets int64 + if i == 0 { + windowNewTickets = int64(block.Header.FreshStake) + } + + // Tally all of the new tickets in all blocks in the window and + // ensure the number of new tickets is a minimum of 1. + prevRetargetHeight := nextHeight - uint32(windowSize*(i+1)) + block = g.ancestorBlock(block, prevRetargetHeight, func(blk *wire.MsgBlock) { + windowNewTickets += int64(blk.Header.FreshStake) + }) + if windowNewTickets <= 0 { + windowNewTickets = 1 + } + + // Calculate the ideal retarget difficulty for the window based + // on the number of new tickets and weight it exponentially by + // multiplying it by 2^(window_number) such that the most recent + // window receives the most weight. + // + // Also, since integer division is being used, shift up the + // number of new tickets 32 bits to avoid losing precision. + // + // NOTE: The real algorithm uses big ints, but these purpose + // built tests won't be using large enough values to overflow, + // so just use uint64s. + adjusted := (windowNewTickets << 32) / targetTicketsPerWindow + adjusted = adjusted << uint64((numWindows-i)*weightAlpha) + weightedTicketsSum += uint64(adjusted) + } + + // Calculate the tickets per window retarget difficulty based on the + // exponential weighted average and shift the result back down 32 bits + // to account for the previous shift up in order to avoid losing + // precision. Then, limit it to the maximum allowed retarget adjustment + // factor. + // + // This is the second metric used in the final calculated difficulty. + nextNewTixDiff := (int64(weightedTicketsSum/weightSum) * curDiff) >> 32 + nextNewTixDiff = g.limitRetarget(curDiff, nextNewTixDiff) + + // Average the previous two metrics using scaled multiplication and + // ensure the result is limited to both the maximum allowed retarget + // adjustment factor and the minimum allowed stake difficulty. + nextDiff := mergeDifficulty(curDiff, nextPoolSizeDiff, nextNewTixDiff) + nextDiff = g.limitRetarget(curDiff, nextDiff) + if nextDiff < g.params.MinimumStakeDiff { + return g.params.MinimumStakeDiff + } + return nextDiff +} + +// hash256prng is a determinstic pseudorandom number generator that uses a +// 256-bit secure hashing function to generate random uint32s starting from +// an initial seed. +type hash256prng struct { + seed chainhash.Hash // Initialization seed + idx uint64 // Hash iterator index + cachedHash chainhash.Hash // Most recently generated hash + hashOffset int // Offset into most recently generated hash +} + +// newHash256PRNG creates a pointer to a newly created hash256PRNG. +func newHash256PRNG(seed []byte) *hash256prng { + // The provided seed is initialized by appending a constant derived from + // the hex representation of pi and hashing the result to give 32 bytes. + // This ensures the PRNG is always doing a short number of rounds + // regardless of input since it will only need to hash small messages + // (less than 64 bytes). + seedHash := chainhash.HashFunc(append(seed, hash256prngSeedConst...)) + return &hash256prng{ + seed: seedHash, + idx: 0, + cachedHash: seedHash, + } +} + +// State returns a hash that represents the current state of the deterministic +// PRNG. +func (hp *hash256prng) State() chainhash.Hash { + // The final state is the hash of the most recently generated hash + // concatenated with both the hash iterator index and the offset into + // the hash. + // + // hash(hp.cachedHash || hp.idx || hp.hashOffset) + finalState := make([]byte, len(hp.cachedHash)+4+1) + copy(finalState, hp.cachedHash[:]) + offset := len(hp.cachedHash) + binary.BigEndian.PutUint32(finalState[offset:], uint32(hp.idx)) + offset += 4 + finalState[offset] = byte(hp.hashOffset) + return chainhash.HashH(finalState) +} + +// Hash256Rand returns a uint32 random number using the pseudorandom number +// generator and updates the state. +func (hp *hash256prng) Hash256Rand() uint32 { + offset := hp.hashOffset * 4 + r := binary.BigEndian.Uint32(hp.cachedHash[offset : offset+4]) + hp.hashOffset++ + + // Generate a new hash and reset the hash position index once it would + // overflow the available bytes in the most recently generated hash. + if hp.hashOffset > 7 { + // Hash of the seed concatenated with the hash iterator index. + // hash(hp.seed || hp.idx) + data := make([]byte, len(hp.seed)+4) + copy(data, hp.seed[:]) + binary.BigEndian.PutUint32(data[len(hp.seed):], uint32(hp.idx)) + hp.cachedHash = chainhash.HashH(data) + hp.idx++ + hp.hashOffset = 0 + } + + // Roll over the entire PRNG by re-hashing the seed when the hash + // iterator index overlows a uint32. + if hp.idx > math.MaxUint32 { + hp.seed = chainhash.HashH(hp.seed[:]) + hp.cachedHash = hp.seed + hp.idx = 0 + } + + return r +} + +// uniformRandom returns a random in the range [0, upperBound) while avoiding +// modulo bias to ensure a normal distribution within the specified range. +func (hp *hash256prng) uniformRandom(upperBound uint32) uint32 { + if upperBound < 2 { + return 0 + } + + // (2^32 - (x*2)) % x == 2^32 % x when x <= 2^31 + min := ((math.MaxUint32 - (upperBound * 2)) + 1) % upperBound + if upperBound > 0x80000000 { + min = 1 + ^upperBound + } + + r := hp.Hash256Rand() + for r < min { + r = hp.Hash256Rand() + } + return r % upperBound +} + +// winningTickets returns a slice of tickets that are required to vote for the +// given block being voted on and live ticket pool and the associated underlying +// deterministic prng state hash. +func winningTickets(voteBlock *wire.MsgBlock, liveTickets []*stakeTicket, numVotes uint16) ([]*stakeTicket, chainhash.Hash, error) { + // Serialize the parent block header used as the seed to the + // deterministic pseudo random number generator for vote selection. + var buf bytes.Buffer + buf.Grow(wire.MaxBlockHeaderPayload) + if err := voteBlock.Header.Serialize(&buf); err != nil { + return nil, chainhash.Hash{}, err + } + + // Ensure the number of live tickets is within the allowable range. + numLiveTickets := uint32(len(liveTickets)) + if numLiveTickets > math.MaxUint32 { + return nil, chainhash.Hash{}, fmt.Errorf("live ticket pool "+ + "has %d tickets which is more than the max allowed of "+ + "%d", len(liveTickets), uint32(math.MaxUint32)) + } + if uint32(numVotes) > numLiveTickets { + return nil, chainhash.Hash{}, fmt.Errorf("live ticket pool "+ + "has %d tickets, while %d are needed to vote", + len(liveTickets), numVotes) + } + + // Construct list of winners by generating successive values from the + // deterministic prng and using them as indices into the sorted live + // ticket pool while skipping any duplicates that might occur. + prng := newHash256PRNG(buf.Bytes()) + winners := make([]*stakeTicket, 0, numVotes) + usedOffsets := make(map[uint32]struct{}) + for uint16(len(winners)) < numVotes { + ticketIndex := prng.uniformRandom(numLiveTickets) + if _, exists := usedOffsets[ticketIndex]; !exists { + usedOffsets[ticketIndex] = struct{}{} + winners = append(winners, liveTickets[ticketIndex]) + } + } + return winners, prng.State(), nil +} + +// calcFinalLotteryState calculates the final lottery state for a set of winning +// tickets and the associated deterministic prng state hash after selecting the +// winners. It is the first 6 bytes of: +// +// blake256(firstTicketHash || ... || lastTicketHash || prngStateHash) +func calcFinalLotteryState(winners []*stakeTicket, prngStateHash chainhash.Hash) [6]byte { + data := make([]byte, (len(winners)+1)*chainhash.HashSize) + for i := 0; i < len(winners); i++ { + h := winners[i].tx.CachedTxHash() + copy(data[chainhash.HashSize*i:], h[:]) + } + copy(data[chainhash.HashSize*len(winners):], prngStateHash[:]) + dataHash := chainhash.HashH(data) + + var finalState [6]byte + copy(finalState[:], dataHash[0:6]) + return finalState +} + +// calcMerkleRoot creates a merkle tree from the slice of transactions and +// returns the root of the tree. +func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { + utilTxns := make([]*hcutil.Tx, 0, len(txns)) + for _, tx := range txns { + utilTxns = append(utilTxns, hcutil.NewTx(tx)) + } + merkles := blockchain.BuildMerkleTreeStore(utilTxns) + return *merkles[len(merkles)-1] +} + +// solveBlock attempts to find a nonce which makes the passed block header hash +// to a value less than the target difficulty. When a successful solution is +// found, true is returned and the nonce field of the passed header is updated +// with the solution. False is returned if no solution exists. +// +// NOTE: This function will never solve blocks with a nonce of 0. This is done +// so the 'NextBlock' function can properly detect when a nonce was modified by +// a munge function. +func solveBlock(header *wire.BlockHeader) bool { + // sbResult is used by the solver goroutines to send results. + type sbResult struct { + found bool + nonce uint32 + } + + // solver accepts a block header and a nonce range to test. It is + // intended to be run as a goroutine. + targetDifficulty := blockchain.CompactToBig(header.Bits) + quit := make(chan bool) + results := make(chan sbResult) + solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { + // We need to modify the nonce field of the header, so make sure + // we work with a copy of the original header. + for i := startNonce; i >= startNonce && i <= stopNonce; i++ { + select { + case <-quit: + results <- sbResult{false, 0} + return + default: + hdr.Nonce = i + hash := hdr.BlockHash() + if blockchain.HashToBig(&hash).Cmp( + targetDifficulty) <= 0 { + + results <- sbResult{true, i} + return + } + } + } + results <- sbResult{false, 0} + } + + startNonce := uint32(1) + stopNonce := uint32(math.MaxUint32) + numCores := uint32(runtime.NumCPU()) + noncesPerCore := (stopNonce - startNonce) / numCores + for i := uint32(0); i < numCores; i++ { + rangeStart := startNonce + (noncesPerCore * i) + rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1 + if i == numCores-1 { + rangeStop = stopNonce + } + go solver(*header, rangeStart, rangeStop) + } + var foundResult bool + for i := uint32(0); i < numCores; i++ { + result := <-results + if !foundResult && result.found { + close(quit) + header.Nonce = result.nonce + foundResult = true + } + } + + return foundResult +} + +// ReplaceWithNVotes returns a function that itself takes a block and modifies +// it by replacing the votes in the stake tree with specified number of votes. +// +// NOTE: This must only be used as a munger to the 'NextBlock' function or it +// will lead to an invalid live ticket pool. To help safeguard against improper +// usage, it will panic if called with a block that does not connect to the +// current tip block. +func (g *Generator) ReplaceWithNVotes(numVotes uint16) func(*wire.MsgBlock) { + return func(b *wire.MsgBlock) { + // Attempt to prevent misuse of this function by ensuring the + // provided block connects to the current tip. + if b.Header.PrevBlock != g.tip.BlockHash() { + panic(fmt.Sprintf("attempt to replace number of votes "+ + "for block %s with parent %s that is not the "+ + "current tip %s", b.BlockHash(), + b.Header.PrevBlock, g.tip.BlockHash())) + } + + // Get the winning tickets for the specified number of votes. + parentBlock := g.tip + winners, _, err := winningTickets(parentBlock, g.liveTickets, + numVotes) + if err != nil { + panic(err) + } + + // Generate vote transactions for the winning tickets. + defaultNumVotes := int(g.params.TicketsPerBlock) + numExisting := len(b.STransactions) - defaultNumVotes + stakeTxns := make([]*wire.MsgTx, 0, numExisting+int(numVotes)) + for _, ticket := range winners { + voteTx := g.createVoteTx(parentBlock, ticket) + stakeTxns = append(stakeTxns, voteTx) + } + + // Add back the original stake transactions other than the + // original stake votes that have been replaced. + stakeTxns = append(stakeTxns, b.STransactions[defaultNumVotes:]...) + + // Update the block with the new stake transactions and the + // header with the new number of votes. + b.STransactions = stakeTxns + b.Header.Voters = numVotes + + // Recalculate the coinbase amount based on the number of new + // votes and update the coinbase so that the adjustment in + // subsidy is accounted for. + height := b.Header.Height + fullSubsidy := g.calcFullSubsidy(height) + devSubsidy := g.calcDevSubsidy(fullSubsidy, height, numVotes) + powSubsidy := g.calcPoWSubsidy(fullSubsidy, height, numVotes) + cbTx := b.Transactions[0] + cbTx.TxIn[0].ValueIn = int64(devSubsidy + powSubsidy) + cbTx.TxOut = nil + g.addCoinbaseTxOutputs(cbTx, height, devSubsidy, powSubsidy) + } +} + +// ReplaceBlockVersion returns a function that itself takes a block and modifies +// it by replacing the stake version of the header. +func ReplaceBlockVersion(newVersion int32) func(*wire.MsgBlock) { + return func(b *wire.MsgBlock) { + b.Header.Version = newVersion + } +} + +// ReplaceStakeVersion returns a function that itself takes a block and modifies +// it by replacing the stake version of the header. +func ReplaceStakeVersion(newVersion uint32) func(*wire.MsgBlock) { + return func(b *wire.MsgBlock) { + b.Header.StakeVersion = newVersion + } +} + +// ReplaceVoteVersions returns a function that itself takes a block and modifies +// it by replacing the voter version of the stake transactions. +// +// NOTE: This must only be used as a munger to the 'NextBlock' function or it +// will lead to an invalid live ticket pool. +func ReplaceVoteVersions(newVersion uint32) func(*wire.MsgBlock) { + return func(b *wire.MsgBlock) { + for _, stx := range b.STransactions { + if isVoteTx(stx) { + stx.TxOut[1].PkScript = voteBitsScript( + voteBitYes, newVersion) + } + } + } +} + +// ReplaceVotes returns a function that itself takes a block and modifies it by +// replacing the voter version and bits of the stake transactions. +// +// NOTE: This must only be used as a munger to the 'NextBlock' function or it +// will lead to an invalid live ticket pool. +func ReplaceVotes(voteBits uint16, newVersion uint32) func(*wire.MsgBlock) { + return func(b *wire.MsgBlock) { + for _, stx := range b.STransactions { + if isVoteTx(stx) { + stx.TxOut[1].PkScript = voteBitsScript(voteBits, + newVersion) + } + } + } +} + +// CreateSpendTx creates a transaction that spends from the provided spendable +// output and includes an additional unique OP_RETURN output to ensure the +// transaction ends up with a unique hash. The public key script is a simple +// OP_TRUE p2sh script which avoids the need to track addresses and signature +// scripts in the tests. The signature script is the opTrueRedeemScript. +func (g *Generator) CreateSpendTx(spend *SpendableOut, fee hcutil.Amount) *wire.MsgTx { + spendTx := wire.NewMsgTx() + spendTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: spend.prevOut, + Sequence: wire.MaxTxInSequenceNum, + ValueIn: int64(spend.amount), + BlockHeight: spend.blockHeight, + BlockIndex: spend.blockIndex, + SignatureScript: opTrueRedeemScript, + }) + spendTx.AddTxOut(wire.NewTxOut(int64(spend.amount-fee), + g.p2shOpTrueScript)) + spendTx.AddTxOut(wire.NewTxOut(0, UniqueOpReturnScript())) + return spendTx +} + +// CreateSpendTxForTx creates a transaction that spends from the first output of +// the provided transaction and includes an additional unique OP_RETURN output +// to ensure the transaction ends up with a unique hash. The public key script +// is a simple OP_TRUE p2sh script which avoids the need to track addresses and +// signature scripts in the tests. This signature script the +// opTrueRedeemScript. +func (g *Generator) CreateSpendTxForTx(tx *wire.MsgTx, blockHeight, txIndex uint32, fee hcutil.Amount) *wire.MsgTx { + spend := MakeSpendableOutForTx(tx, blockHeight, txIndex, 0) + return g.CreateSpendTx(&spend, fee) +} + +// removeTicket removes the passed index from the provided slice of tickets and +// returns the resulting slice. This is an in-place modification. +func removeTicket(tickets []*stakeTicket, index int) []*stakeTicket { + copy(tickets[index:], tickets[index+1:]) + tickets[len(tickets)-1] = nil // Prevent memory leak + tickets = tickets[:len(tickets)-1] + return tickets +} + +// connectLiveTickets updates the live ticket pool for a new tip block by +// removing tickets that are now expired from it, removing the passed winners +// from it, adding any immature tickets which are now mature to it, and +// resorting it. +func (g *Generator) connectLiveTickets(blockHash *chainhash.Hash, height uint32, winners, purchases []*stakeTicket) { + // Move expired tickets from the live ticket pool to the expired ticket + // pool. + ticketMaturity := uint32(g.params.TicketMaturity) + ticketExpiry := g.params.TicketExpiry + for i := 0; i < len(g.liveTickets); i++ { + ticket := g.liveTickets[i] + liveHeight := ticket.blockHeight + ticketMaturity + expireHeight := liveHeight + ticketExpiry + if height >= expireHeight { + g.liveTickets = removeTicket(g.liveTickets, i) + g.expiredTickets = append(g.expiredTickets, ticket) + + // This is required because the ticket at the current + // offset was just removed from the slice that is being + // iterated, so adjust the offset down one accordingly. + i-- + } + } + + // Move winning tickets from the live ticket pool to won tickets pool. + for i := 0; i < len(g.liveTickets); i++ { + ticket := g.liveTickets[i] + for _, winner := range winners { + if ticket.tx.CachedTxHash() == winner.tx.CachedTxHash() { + g.liveTickets = removeTicket(g.liveTickets, i) + + // This is required because the ticket at the + // current offset was just removed from the + // slice that is being iterated, so adjust the + // offset down one accordingly. + i-- + break + } + } + } + g.wonTickets[*blockHash] = winners + + // Move immature tickets which are now mature to the live ticket pool. + for i := 0; i < len(g.immatureTickets); i++ { + ticket := g.immatureTickets[i] + liveHeight := ticket.blockHeight + ticketMaturity + if height >= liveHeight { + g.immatureTickets = removeTicket(g.immatureTickets, i) + g.liveTickets = append(g.liveTickets, ticket) + + // This is required because the ticket at the current + // offset was just removed from the slice that is being + // iterated, so adjust the offset down one accordingly. + i-- + } + } + + // Resort the ticket pool now that all live ticket pool manipulations + // are done. + sort.Sort(stakeTicketSorter(g.liveTickets)) + + // Add new ticket purchases to the immature ticket pool. + g.immatureTickets = append(g.immatureTickets, purchases...) +} + +// connectBlockTickets updates the live ticket pool and associated data structs +// by for the passed block. It will panic if the specified block does not +// connect to the current tip block. +func (g *Generator) connectBlockTickets(b *wire.MsgBlock) { + // Attempt to prevent misuse of this function by ensuring the provided + // block connects to the current tip. + if b.Header.PrevBlock != g.tip.BlockHash() { + panic(fmt.Sprintf("attempt to connect block %s with parent %s "+ + "that is not the current tip %s", b.BlockHash(), + b.Header.PrevBlock, g.tip.BlockHash())) + } + + // Get all of the winning tickets for the block. + numVotes := g.params.TicketsPerBlock + winners, _, err := winningTickets(g.tip, g.liveTickets, numVotes) + if err != nil { + panic(err) + } + + // Extract the ticket purchases (sstx) from the block. + height := b.Header.Height + var purchases []*stakeTicket + for txIdx, tx := range b.STransactions { + if isTicketPurchaseTx(tx) { + ticket := &stakeTicket{tx, height, uint32(txIdx)} + purchases = append(purchases, ticket) + } + } + + // Update the live ticket pool and associated data structures. + blockHash := b.BlockHash() + g.connectLiveTickets(&blockHash, height, winners, purchases) +} + +// disconnectBlockTickets updates the live ticket pool and associated data +// structs by unwinding the passed block, which must be the current tip block. +// It will panic if the specified block is not the current tip block. +func (g *Generator) disconnectBlockTickets(b *wire.MsgBlock) { + // Attempt to prevent misuse of this function by ensuring the provided + // block is the current tip. + if b != g.tip { + panic(fmt.Sprintf("attempt to disconnect block %s that is not "+ + "the current tip %s", b.BlockHash(), g.tip.BlockHash())) + } + + // Remove tickets created in the block from the immature ticket pool. + blockHeight := b.Header.Height + for i := 0; i < len(g.immatureTickets); i++ { + ticket := g.immatureTickets[i] + if ticket.blockHeight == blockHeight { + g.immatureTickets = removeTicket(g.immatureTickets, i) + + // This is required because the ticket at the current + // offset was just removed from the slice that is being + // iterated, so adjust the offset down one accordingly. + i-- + } + } + + // Move tickets that are no longer mature from the live ticket pool to + // the immature ticket pool. + prevBlockHeight := blockHeight - 1 + ticketMaturity := uint32(g.params.TicketMaturity) + for i := 0; i < len(g.liveTickets); i++ { + ticket := g.liveTickets[i] + liveHeight := ticket.blockHeight + ticketMaturity + if prevBlockHeight < liveHeight { + g.liveTickets = removeTicket(g.liveTickets, i) + g.immatureTickets = append(g.immatureTickets, ticket) + + // This is required because the ticket at the current + // offset was just removed from the slice that is being + // iterated, so adjust the offset down one accordingly. + i-- + } + } + + // Move tickets that are no longer expired from the expired ticket pool + // to the live ticket pool. + ticketExpiry := g.params.TicketExpiry + for i := 0; i < len(g.expiredTickets); i++ { + ticket := g.expiredTickets[i] + liveHeight := ticket.blockHeight + ticketMaturity + expireHeight := liveHeight + ticketExpiry + if prevBlockHeight < expireHeight { + g.expiredTickets = removeTicket(g.expiredTickets, i) + g.liveTickets = append(g.liveTickets, ticket) + + // This is required because the ticket at the current + // offset was just removed from the slice that is being + // iterated, so adjust the offset down one accordingly. + i-- + } + } + + // Add the winning tickets consumed by the block back to the live ticket + // pool. + blockHash := b.BlockHash() + g.liveTickets = append(g.liveTickets, g.wonTickets[blockHash]...) + delete(g.wonTickets, blockHash) + + // Resort the ticket pool now that all live ticket pool manipulations + // are done. + sort.Sort(stakeTicketSorter(g.liveTickets)) +} + +// originalParent returns the original block the passed block was built from. +// This is necessary because callers might change the previous block hash in a +// munger which would cause the like ticket pool to be reconstructed improperly. +func (g *Generator) originalParent(b *wire.MsgBlock) *wire.MsgBlock { + parentHash, ok := g.originalParents[b.BlockHash()] + if !ok { + parentHash = b.Header.PrevBlock + } + return g.BlockByHash(&parentHash) +} + +// SetTip changes the tip of the instance to the block with the provided name. +// This is useful since the tip is used for things such as generating subsequent +// blocks. +func (g *Generator) SetTip(blockName string) { + // Nothing to do if already the tip. + if blockName == g.tipName { + return + } + + newTip := g.blocksByName[blockName] + if newTip == nil { + panic(fmt.Sprintf("tip block name %s does not exist", blockName)) + } + + // Create a list of blocks to disconnect and blocks to connect in order + // to switch to the new tip. + var connect, disconnect []*wire.MsgBlock + oldBranch, newBranch := g.tip, newTip + for oldBranch != newBranch { + // As long as the two branches are not at the same height, add + // the tip of the longest one to the appropriate connect or + // disconnect list and move its tip back to its previous block. + if oldBranch.Header.Height > newBranch.Header.Height { + disconnect = append(disconnect, oldBranch) + oldBranch = g.originalParent(oldBranch) + continue + } else if newBranch.Header.Height > oldBranch.Header.Height { + connect = append(connect, newBranch) + newBranch = g.originalParent(newBranch) + continue + } + + // At this point the two branches have the same height, so add + // each tip to the appropriate connect or disconnect list and + // the tips to their previous block. + disconnect = append(disconnect, oldBranch) + oldBranch = g.originalParent(oldBranch) + connect = append(connect, newBranch) + newBranch = g.originalParent(newBranch) + } + + // Update the live ticket pool and associated data structs by + // disconnecting all blocks back to the fork point. + for _, block := range disconnect { + g.disconnectBlockTickets(block) + g.tip = g.originalParent(block) + } + + // Update the live ticket pool and associated data structs by connecting + // all blocks after the fork point up to the new tip. The list of + // blocks to connect is iterated in reverse order, because it was + // constructed in reverse, and the blocks need to be connected in the + // order in which they build the chain. + for i := len(connect) - 1; i >= 0; i-- { + block := connect[i] + g.connectBlockTickets(block) + g.tip = block + } + + // Ensure the tip is the expected new tip and set the associated name. + if g.tip != newTip { + panic(fmt.Sprintf("tip %s is not expected new tip %s", + g.tip.BlockHash(), newTip.BlockHash())) + } + g.tipName = blockName +} + +// NextBlock builds a new block that extends the current tip associated with the +// generator and updates the generator's tip to the newly generated block. +// +// The block will include the following: +// - A coinbase with the following outputs: +// - One that pays the required 10% subsidy to the dev org +// - One that contains a standard coinbase OP_RETURN script +// - Six that pay the required 60% subsidy to an OP_TRUE p2sh script +// +// - When a spendable output is provided: +// - A transaction that spends from the provided output the following outputs: +// - One that pays the inputs amount minus 1 atom to an OP_TRUE p2sh script +// +// - Once the coinbase maturity has been reached: +// - A ticket purchase transaction (sstx) for each provided ticket spendable +// output with the following outputs: +// - One OP_SSTX output that grants voting rights to an OP_TRUE p2sh script +// - One OP_RETURN output that contains the required commitment and pays +// the subsidy to an OP_TRUE p2sh script +// - One OP_SSTXCHANGE output that sends change to an OP_TRUE p2sh script +// +// - Once the stake validation height has been reached: +// - 5 vote transactions (ssgen) as required according to the live ticket +// pool and vote selection rules with the following outputs: +// - One OP_RETURN followed by the block hash and height being voted on +// - One OP_RETURN followed by the vote bits +// - One or more OP_SSGEN outputs with the payouts according to the original +// ticket commitments +// +// Additionally, if one or more munge functions are specified, they will be +// invoked with the block prior to solving it. This provides callers with the +// opportunity to modify the block which is especially useful for testing. +// +// In order to simply the logic in the munge functions, the following rules are +// applied after all munge functions have been invoked: +// - The merkle root will be recalculated unless it was manually changed +// - The stake root will be recalculated unless it was manually changed +// - The size of the block will be recalculated unless it was manually changed +// - The block will be solved unless the nonce was changed +func (g *Generator) NextBlock(blockName string, spend *SpendableOut, ticketSpends []SpendableOut, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock { + // Calculate the next required stake difficulty (aka ticket price). + ticketPrice := hcutil.Amount(g.calcNextRequiredStakeDifficulty()) + + // Generate the appropriate votes and ticket purchases based on the + // current tip block and provided ticket spendable outputs. + var ticketWinners []*stakeTicket + var stakeTxns []*wire.MsgTx + var finalState [6]byte + nextHeight := g.tip.Header.Height + 1 + if nextHeight > uint32(g.params.CoinbaseMaturity) { + // Generate votes once the stake validation height has been + // reached. + if int64(nextHeight) >= g.params.StakeValidationHeight { + // Generate and add the vote transactions for the + // winning tickets to the stake tree. + numVotes := g.params.TicketsPerBlock + winners, stateHash, err := winningTickets(g.tip, + g.liveTickets, numVotes) + if err != nil { + panic(err) + } + ticketWinners = winners + for _, ticket := range winners { + voteTx := g.createVoteTx(g.tip, ticket) + stakeTxns = append(stakeTxns, voteTx) + } + + // Calculate the final lottery state hash for use in the + // block header. + finalState = calcFinalLotteryState(winners, stateHash) + } + + // Generate ticket purchases (sstx) using the provided spendable + // outputs. + if ticketSpends != nil { + const ticketFee = hcutil.Amount(2) + for i := 0; i < len(ticketSpends); i++ { + out := &ticketSpends[i] + purchaseTx := g.createTicketPurchaseTx(out, + ticketPrice, ticketFee) + stakeTxns = append(stakeTxns, purchaseTx) + } + } + } + + // Create stake tickets for the ticket purchases (sstx), count the + // votes (ssgen) and ticket revocations (ssrtx), and calculate the + // total PoW fees generated by the stake transactions. + var numVotes uint16 + var numTicketRevocations uint8 + var ticketPurchases []*stakeTicket + var stakeTreeFees hcutil.Amount + for txIdx, tx := range stakeTxns { + switch { + case isVoteTx(tx): + numVotes++ + case isTicketPurchaseTx(tx): + ticket := &stakeTicket{tx, nextHeight, uint32(txIdx)} + ticketPurchases = append(ticketPurchases, ticket) + case isRevocationTx(tx): + numTicketRevocations++ + } + + // Calculate any fees for the transaction. + var inputSum, outputSum hcutil.Amount + for _, txIn := range tx.TxIn { + inputSum += hcutil.Amount(txIn.ValueIn) + } + for _, txOut := range tx.TxOut { + outputSum += hcutil.Amount(txOut.Value) + } + stakeTreeFees += (inputSum - outputSum) + } + + // Create a standard coinbase and spending transaction. + var regularTxns []*wire.MsgTx + { + // Create coinbase transaction for the block with no additional + // dev or pow subsidy. + coinbaseTx := g.CreateCoinbaseTx(nextHeight, numVotes) + regularTxns = []*wire.MsgTx{coinbaseTx} + + // Increase the PoW subsidy to account for any fees in the stake + // tree. + coinbaseTx.TxOut[2].Value += int64(stakeTreeFees) + + // Create a transaction to spend the provided utxo if needed. + if spend != nil { + // Create the transaction with a fee of 1 atom for the + // miner and increase the PoW subsidy accordingly. + fee := hcutil.Amount(1) + coinbaseTx.TxOut[2].Value += int64(fee) + + // Create a transaction that spends from the provided + // spendable output and includes an additional unique + // OP_RETURN output to ensure the transaction ends up + // with a unique hash, then add it to the list of + // transactions to include in the block. The script is + // a simple OP_TRUE p2sh script in order to avoid the + // need to track addresses and signature scripts in the + // tests. + spendTx := g.CreateSpendTx(spend, fee) + regularTxns = append(regularTxns, spendTx) + } + } + + // Use a timestamp that is 7/8 of target timespan after the previous + // block unless this is the first block in which case the current time + // is used or the proof-of-work difficulty parameters have been adjusted + // such that it's greater than the max 2 hours worth of blocks that can + // be tested in which case one second is used. This helps maintain the + // retarget difficulty low as needed. Also, ensure the timestamp is + // limited to one second precision. + var ts time.Time + if nextHeight == 1 { + ts = time.Now() + } else { + if g.params.WorkDiffWindowSize > 7200 { + ts = g.tip.Header.Timestamp.Add(time.Second) + } else { + addDuration := g.params.TargetTimespan * 7 / 8 + ts = g.tip.Header.Timestamp.Add(addDuration) + } + } + ts = time.Unix(ts.Unix(), 0) + + // Create the unsolved block. + prevHash := g.tip.BlockHash() + block := wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: prevHash, + MerkleRoot: calcMerkleRoot(regularTxns), + StakeRoot: calcMerkleRoot(stakeTxns), + VoteBits: 1, + FinalState: finalState, + Voters: numVotes, + FreshStake: uint8(len(ticketPurchases)), + Revocations: numTicketRevocations, + PoolSize: uint32(len(g.liveTickets)), + Bits: g.calcNextRequiredDifficulty(), + SBits: int64(ticketPrice), + Height: nextHeight, + Size: 0, // Filled in below. + Timestamp: ts, + Nonce: 0, // To be solved. + ExtraData: [32]byte{}, + StakeVersion: 0, + }, + Transactions: regularTxns, + STransactions: stakeTxns, + } + block.Header.Size = uint32(block.SerializeSize()) + + // Perform any block munging just before solving. Only recalculate the + // merkle roots and block size if they weren't manually changed by a + // munge function. + curMerkleRoot := block.Header.MerkleRoot + curStakeRoot := block.Header.StakeRoot + curSize := block.Header.Size + curNonce := block.Header.Nonce + for _, f := range mungers { + f(&block) + } + if block.Header.MerkleRoot == curMerkleRoot { + block.Header.MerkleRoot = calcMerkleRoot(block.Transactions) + } + if block.Header.StakeRoot == curStakeRoot { + block.Header.StakeRoot = calcMerkleRoot(block.STransactions) + } + if block.Header.Size == curSize { + block.Header.Size = uint32(block.SerializeSize()) + } + + // Only solve the block if the nonce wasn't manually changed by a munge + // function. + if block.Header.Nonce == curNonce && !solveBlock(&block.Header) { + panic(fmt.Sprintf("Unable to solve block at height %d", + block.Header.Height)) + } + + // Update generator state and return the block. + blockHash := block.BlockHash() + if block.Header.PrevBlock != prevHash { + // Save the orignal block this one was built from if it was + // manually changed in a munger so the code which deals with + // updating the live tickets when changing the tip has access to + // it. + g.originalParents[blockHash] = prevHash + } + g.connectLiveTickets(&blockHash, nextHeight, ticketWinners, + ticketPurchases) + g.blocks[blockHash] = &block + g.blocksByName[blockName] = &block + g.tip = &block + g.tipName = blockName + return &block +} + +// CreatePremineBlock generates the first block of the chain with the required +// premine payouts. The additional amount parameter can be used to create a +// block that is otherwise a completely valid premine block except it adds the +// extra amount to each payout and thus create a block that violates consensus. +func (g *Generator) CreatePremineBlock(blockName string, additionalAmount hcutil.Amount) *wire.MsgBlock { + coinbaseTx := wire.NewMsgTx() + coinbaseTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex, wire.TxTreeRegular), + Sequence: wire.MaxTxInSequenceNum, + ValueIn: 0, // Updated below. + BlockHeight: wire.NullBlockHeight, + BlockIndex: wire.NullBlockIndex, + SignatureScript: coinbaseSigScript, + }) + + // Add each required output and tally the total payouts for the coinbase + // in order to set the input value appropriately. + var totalSubsidy hcutil.Amount + for _, payout := range g.params.BlockOneLedger { + payoutAddr, err := hcutil.DecodeAddress(payout.Address) + if err != nil { + panic(err) + } + pkScript, err := txscript.PayToAddrScript(payoutAddr) + if err != nil { + panic(err) + } + coinbaseTx.AddTxOut(&wire.TxOut{ + Value: payout.Amount + int64(additionalAmount), + Version: 0, + PkScript: pkScript, + }) + + totalSubsidy += hcutil.Amount(payout.Amount) + } + coinbaseTx.TxIn[0].ValueIn = int64(totalSubsidy) + + // Generate the block with the specially created regular transactions. + return g.NextBlock(blockName, nil, nil, func(b *wire.MsgBlock) { + b.Transactions = []*wire.MsgTx{coinbaseTx} + }) +} + +// UpdateBlockState manually updates the generator state to remove all internal +// map references to a block via its old hash and insert new ones for the new +// block hash. This is useful if the test code has to manually change a block +// after 'NextBlock' has returned. +func (g *Generator) UpdateBlockState(oldBlockName string, oldBlockHash chainhash.Hash, newBlockName string, newBlock *wire.MsgBlock) { + // Remove existing entries. + wonTickets := g.wonTickets[oldBlockHash] + delete(g.blocks, oldBlockHash) + delete(g.blocksByName, oldBlockName) + delete(g.wonTickets, oldBlockHash) + + // Add new entries. + newBlockHash := newBlock.BlockHash() + g.blocks[newBlockHash] = newBlock + g.blocksByName[newBlockName] = newBlock + g.wonTickets[newBlockHash] = wonTickets +} + +// OldestCoinbaseOuts removes the oldest set of coinbase proof-of-work outputs +// that was previously saved to the generator and returns the set as a slice. +func (g *Generator) OldestCoinbaseOuts() []SpendableOut { + outs := g.spendableOuts[0] + g.spendableOuts = g.spendableOuts[1:] + return outs +} + +// NumSpendableCoinbaseOuts returns the number of proof-of-work outputs that +// were previously saved to the generated but have not yet been collected. +func (g *Generator) NumSpendableCoinbaseOuts() int { + return len(g.spendableOuts) +} + +// saveCoinbaseOuts adds the proof-of-work outputs of the coinbase tx in the +// passed block to the list of spendable outputs. +func (g *Generator) saveCoinbaseOuts(b *wire.MsgBlock) { + g.spendableOuts = append(g.spendableOuts, []SpendableOut{ + MakeSpendableOut(b, 0, 2), + MakeSpendableOut(b, 0, 3), + MakeSpendableOut(b, 0, 4), + MakeSpendableOut(b, 0, 5), + MakeSpendableOut(b, 0, 6), + MakeSpendableOut(b, 0, 7), + }) + g.prevCollectedHash = b.BlockHash() +} + +// SaveTipCoinbaseOuts adds the proof-of-work outputs of the coinbase tx in the +// current tip block to the list of spendable outputs. +func (g *Generator) SaveTipCoinbaseOuts() { + g.saveCoinbaseOuts(g.tip) +} + +// SaveSpendableCoinbaseOuts adds all proof-of-work coinbase outputs starting +// from the block after the last block that had its coinbase outputs collected +// and ending at the current tip. This is useful to batch the collection of the +// outputs once the tests reach a stable point so they don't have to manually +// add them for the right tests which will ultimately end up being the best +// chain. +func (g *Generator) SaveSpendableCoinbaseOuts() { + // Loop through the ancestors of the current tip until the + // reaching the block that has already had the coinbase outputs + // collected. + var collectBlocks []*wire.MsgBlock + for b := g.tip; b != nil; b = g.blocks[b.Header.PrevBlock] { + if b.BlockHash() == g.prevCollectedHash { + break + } + collectBlocks = append(collectBlocks, b) + } + for i := range collectBlocks { + g.saveCoinbaseOuts(collectBlocks[len(collectBlocks)-1-i]) + } +} + +// AssertTipHeight panics if the current tip block associated with the generator +// does not have the specified height. +func (g *Generator) AssertTipHeight(expected uint32) { + height := g.tip.Header.Height + if height != expected { + panic(fmt.Sprintf("height for block %q is %d instead of "+ + "expected %d", g.tipName, height, expected)) + } +} + +// AssertScriptSigOpsCount panics if the provided script does not have the +// specified number of signature operations. +func (g *Generator) AssertScriptSigOpsCount(script []byte, expected int) { + numSigOps := txscript.GetSigOpCount(script) + if numSigOps != expected { + _, file, line, _ := runtime.Caller(1) + panic(fmt.Sprintf("assertion failed at %s:%d: generated number "+ + "of sigops for script is %d instead of expected %d", + file, line, numSigOps, expected)) + } +} + +// countBlockSigOps returns the number of legacy signature operations in the +// scripts in the passed block. +func countBlockSigOps(block *wire.MsgBlock) int { + totalSigOps := 0 + for _, tx := range block.Transactions { + for _, txIn := range tx.TxIn { + numSigOps := txscript.GetSigOpCount(txIn.SignatureScript) + totalSigOps += numSigOps + } + for _, txOut := range tx.TxOut { + numSigOps := txscript.GetSigOpCount(txOut.PkScript) + totalSigOps += numSigOps + } + } + + return totalSigOps +} + +// AssertTipBlockSigOpsCount panics if the current tip block associated with the +// generator does not have the specified number of signature operations. +func (g *Generator) AssertTipBlockSigOpsCount(expected int) { + numSigOps := countBlockSigOps(g.tip) + if numSigOps != expected { + panic(fmt.Sprintf("generated number of sigops for block %q "+ + "(height %d) is %d instead of expected %d", g.tipName, + g.tip.Header.Height, numSigOps, expected)) + } +} + +// AssertTipBlockSize panics if the if the current tip block associated with the +// generator does not have the specified size when serialized. +func (g *Generator) AssertTipBlockSize(expected int) { + serializeSize := g.tip.SerializeSize() + if serializeSize != expected { + panic(fmt.Sprintf("block size of block %q (height %d) is %d "+ + "instead of expected %d", g.tipName, + g.tip.Header.Height, serializeSize, expected)) + } +} + +// AssertTipBlockNumTxns panics if the number of transactions in the current tip +// block associated with the generator does not match the specified value. +func (g *Generator) AssertTipBlockNumTxns(expected int) { + numTxns := len(g.tip.Transactions) + if numTxns != expected { + panic(fmt.Sprintf("number of txns in block %q (height %d) is "+ + "%d instead of expected %d", g.tipName, + g.tip.Header.Height, numTxns, expected)) + } +} + +// AssertTipBlockHash panics if the current tip block associated with the +// generator does not match the specified hash. +func (g *Generator) AssertTipBlockHash(expected chainhash.Hash) { + hash := g.tip.BlockHash() + if hash != expected { + panic(fmt.Sprintf("block hash of block %q (height %d) is %v "+ + "instead of expected %v", g.tipName, + g.tip.Header.Height, hash, expected)) + } +} + +// AssertTipBlockMerkleRoot panics if the merkle root in header of the current +// tip block associated with the generator does not match the specified hash. +func (g *Generator) AssertTipBlockMerkleRoot(expected chainhash.Hash) { + hash := g.tip.Header.MerkleRoot + if hash != expected { + panic(fmt.Sprintf("merkle root of block %q (height %d) is %v "+ + "instead of expected %v", g.tipName, + g.tip.Header.Height, hash, expected)) + } +} + +// AssertTipBlockTxOutOpReturn panics if the current tip block associated with +// the generator does not have an OP_RETURN script for the transaction output at +// the provided tx index and output index. +func (g *Generator) AssertTipBlockTxOutOpReturn(txIndex, txOutIndex uint32) { + if txIndex >= uint32(len(g.tip.Transactions)) { + panic(fmt.Sprintf("Transaction index %d in block %q "+ + "(height %d) does not exist", txIndex, g.tipName, + g.tip.Header.Height)) + } + + tx := g.tip.Transactions[txIndex] + if txOutIndex >= uint32(len(tx.TxOut)) { + panic(fmt.Sprintf("transaction index %d output %d in block %q "+ + "(height %d) does not exist", txIndex, txOutIndex, + g.tipName, g.tip.Header.Height)) + } + + txOut := tx.TxOut[txOutIndex] + if txOut.PkScript[0] != txscript.OP_RETURN { + panic(fmt.Sprintf("transaction index %d output %d in block %q "+ + "(height %d) is not an OP_RETURN", txIndex, txOutIndex, + g.tipName, g.tip.Header.Height)) + } +} + +// AssertStakeVersion panics if the current tip block associated with the +// generator does not have the specified stake version in the header. +func (g *Generator) AssertStakeVersion(expected uint32) { + stakeVersion := g.tip.Header.StakeVersion + if stakeVersion != expected { + panic(fmt.Sprintf("stake version for block %q is %d instead of "+ + "expected %d", g.tipName, stakeVersion, expected)) + } +} + +// AssertBlockVersion panics if the current tip block associated with the +// generator does not have the specified version. +func (g *Generator) AssertBlockVersion(expected int32) { + blockVersion := g.tip.Header.Version + if blockVersion != expected { + panic(fmt.Sprintf("block version for block %q is %d instead of "+ + "expected %d", g.tipName, blockVersion, expected)) + } +} diff --git a/blockchain/chainio.go b/blockchain/chainio.go index d231b803..028e1e9e 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1,2303 +1,2303 @@ -// Copyright (c) 2015-2016 The btcsuite developers -// Copyright (c) 2016-2017 The Decred developers -// Copyright (c) 2018-2020 The Hc developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package blockchain - -import ( - "bytes" - "encoding/binary" - "fmt" - "math/big" - "reflect" - "sort" - "time" - - "github.com/HcashOrg/hcd/blockchain/internal/dbnamespace" - "github.com/HcashOrg/hcd/blockchain/stake" - "github.com/HcashOrg/hcd/chaincfg" - "github.com/HcashOrg/hcd/chaincfg/chainhash" - "github.com/HcashOrg/hcd/database" - "github.com/HcashOrg/hcd/hcutil" - "github.com/HcashOrg/hcd/wire" -) - -var ( - // thresholdBucketName is the name of the db bucket used to house cached - // threshold states. - thresholdBucketName = []byte("thresholdstate") - - // numDeploymentsKeyName is the name of the db key used to store the - // number of saved deployment caches. - numDeploymentsKeyName = []byte("numdeployments") - - // deploymentBucketName is the name of the db bucket used to house the - // cached threshold states for the actively defined rule deployments. - deploymentBucketName = []byte("deploymentcache") - // deploymentStateKeyName is the name of the db key used to store the - // deployment state associated with the threshold cache for a given rule - // deployment. - deploymentStateKeyName = []byte("deploymentstate") - - // byteOrder is the preferred byte order used for serializing numeric - // fields for storage in the database. - byteOrder = binary.LittleEndian -) - -const ( - // upgradeStartedBit if the bit flag for whether or not a database - // upgrade is in progress. It is used to determine if the database - // is in an inconsistent state from the update. - upgradeStartedBit = 0x80000000 - - // currentDatabaseVersion indicates what the current database - // version is. - currentDatabaseVersion = 2 -) - -// errNotInMainChain signifies that a block hash or height that is not in the -// main chain was requested. -type errNotInMainChain string - -// Error implements the error interface. -func (e errNotInMainChain) Error() string { - return string(e) -} - -// isNotInMainChainErr returns whether or not the passed error is an -// errNotInMainChain error. -func isNotInMainChainErr(err error) bool { - _, ok := err.(errNotInMainChain) - return ok -} - -// errDeserialize signifies that a problem was encountered when deserializing -// data. -type errDeserialize string - -// Error implements the error interface. -func (e errDeserialize) Error() string { - return string(e) -} - -// isDeserializeErr returns whether or not the passed error is an errDeserialize -// error. -func isDeserializeErr(err error) bool { - _, ok := err.(errDeserialize) - return ok -} - -// isDbBucketNotFoundErr returns whether or not the passed error is a -// database.Error with an error code of database.ErrBucketNotFound. -func isDbBucketNotFoundErr(err error) bool { - dbErr, ok := err.(database.Error) - return ok && dbErr.ErrorCode == database.ErrBucketNotFound -} - -// ----------------------------------------------------------------------------- -// The staking system requires some extra information to be stored for tickets -// to maintain consensus rules. The full set of minimal outputs are thus required -// in order for the chain to work correctly. A 'minimal output' is simply the -// script version, pubkey script, and amount. - -// serializeSizeForMinimalOutputs calculates the number of bytes needed to -// serialize a transaction to its minimal outputs. -func serializeSizeForMinimalOutputs(tx *hcutil.Tx) int { - sz := serializeSizeVLQ(uint64(len(tx.MsgTx().TxOut))) - for _, out := range tx.MsgTx().TxOut { - sz += serializeSizeVLQ(compressTxOutAmount(uint64(out.Value))) - sz += serializeSizeVLQ(uint64(out.Version)) - sz += serializeSizeVLQ(uint64(len(out.PkScript))) - sz += len(out.PkScript) - } - - return sz -} - -// putTxToMinimalOutputs serializes a transaction to its minimal outputs. -// It returns the amount of data written. The function will panic if it writes -// beyond the bounds of the passed memory. -func putTxToMinimalOutputs(target []byte, tx *hcutil.Tx) int { - offset := putVLQ(target, uint64(len(tx.MsgTx().TxOut))) - for _, out := range tx.MsgTx().TxOut { - offset += putVLQ(target[offset:], compressTxOutAmount(uint64(out.Value))) - offset += putVLQ(target[offset:], uint64(out.Version)) - offset += putVLQ(target[offset:], uint64(len(out.PkScript))) - copy(target[offset:], out.PkScript) - offset += len(out.PkScript) - } - - return offset -} - -// deserializeToMinimalOutputs deserializes a series of minimal outputs to their -// decompressed, deserialized state and stores them in a slice. It also returns -// the amount of data read. The function will panic if it reads beyond the bounds -// of the passed memory. -func deserializeToMinimalOutputs(serialized []byte) ([]*stake.MinimalOutput, int) { - numOutputs, offset := deserializeVLQ(serialized) - minOuts := make([]*stake.MinimalOutput, int(numOutputs)) - for i := 0; i < int(numOutputs); i++ { - amountComp, bytesRead := deserializeVLQ(serialized[offset:]) - amount := decompressTxOutAmount(amountComp) - offset += bytesRead - - version, bytesRead := deserializeVLQ(serialized[offset:]) - offset += bytesRead - - scriptSize, bytesRead := deserializeVLQ(serialized[offset:]) - offset += bytesRead - - pkScript := make([]byte, int(scriptSize)) - copy(pkScript, serialized[offset:offset+int(scriptSize)]) - offset += int(scriptSize) - - minOuts[i] = &stake.MinimalOutput{ - Value: int64(amount), - Version: uint16(version), - PkScript: pkScript, - } - } - - return minOuts, offset -} - -// readDeserializeSizeOfMinimalOutputs reads the size of the stored set of -// minimal outputs without allocating memory for the structs themselves. It -// will panic if the function reads outside of memory bounds. -func readDeserializeSizeOfMinimalOutputs(serialized []byte) int { - numOutputs, offset := deserializeVLQ(serialized) - for i := 0; i < int(numOutputs); i++ { - // Amount - _, bytesRead := deserializeVLQ(serialized[offset:]) - offset += bytesRead - - // Script version - _, bytesRead = deserializeVLQ(serialized[offset:]) - offset += bytesRead - - // Script - var scriptSize uint64 - scriptSize, bytesRead = deserializeVLQ(serialized[offset:]) - offset += bytesRead - offset += int(scriptSize) - } - - return offset -} - -// ConvertUtxosToMinimalOutputs converts the contents of a UTX to a series of -// minimal outputs. It does this so that these can be passed to stake subpackage -// functions, where they will be evaluated for correctness. -func ConvertUtxosToMinimalOutputs(entry *UtxoEntry) []*stake.MinimalOutput { - minOuts, _ := deserializeToMinimalOutputs(entry.stakeExtra) - - return minOuts -} - -// ----------------------------------------------------------------------------- -// The transaction spend journal consists of an entry for each block connected -// to the main chain which contains the transaction outputs the block spends -// serialized such that the order is the reverse of the order they were spent. -// -// This is required because reorganizing the chain necessarily entails -// disconnecting blocks to get back to the point of the fork which implies -// unspending all of the transaction outputs that each block previously spent. -// Since the utxo set, by definition, only contains unspent transaction outputs, -// the spent transaction outputs must be resurrected from somewhere. There is -// more than one way this could be done, however this is the most straight -// forward method that does not require having a transaction index and unpruned -// blockchain. -// -// NOTE: This format is NOT self describing. The additional details such as -// the number of entries (transaction inputs) are expected to come from the -// block itself and the utxo set. The rationale in doing this is to save a -// significant amount of space. This is also the reason the spent outputs are -// serialized in the reverse order they are spent because later transactions -// are allowed to spend outputs from earlier ones in the same block. -// -// The serialized format is: -// -// [