Added CoinSelector interface and a few simple algos as a sub-package
This commit contains a basic definition for CoinSelector along with some utility classes and some basic algos to make creating transactions from a set of available unspent outpoints easier. Thanks to @dajohi, @davec, @jrick for all the feedback and suggestions regarding interfaces, organization, optimization, comments and documentation.
This commit is contained in:
parent
60d4bed78f
commit
02a1584784
6 changed files with 812 additions and 22 deletions
96
coinset/README.md
Normal file
96
coinset/README.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
coinset
|
||||||
|
=======
|
||||||
|
|
||||||
|
Package coinset provides bitcoin-specific convenience functions for selecting
|
||||||
|
from and managing sets of unspent transaction outpoints (UTXOs).
|
||||||
|
|
||||||
|
A comprehensive suite of tests is provided to ensure proper functionality. See
|
||||||
|
`test_coverage.txt` for the gocov coverage report. Alternatively, if you are
|
||||||
|
running a POSIX OS, you can run the `cov_report.sh` script for a real-time
|
||||||
|
report. Package coinset is licensed under the liberal ISC license.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://godoc.org/github.com/conformal/btcutil/coinset
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/conformal/btcutil/coinset
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/conformal/btcutil/coinset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Each unspent transaction outpoint is represented by the Coin interface. An
|
||||||
|
example of a concrete type that implements Coin is coinset.SimpleCoin.
|
||||||
|
|
||||||
|
The typical use case for this library is for creating raw bitcoin transactions
|
||||||
|
given a set of Coins that may be spent by the user, for example as below:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
var unspentCoins = []coinset.Coin{ ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
When the user needs to spend a certain amount, they will need to select a
|
||||||
|
subset of these coins which contain at least that value. CoinSelector is
|
||||||
|
an interface that represents types that implement coin selection algos,
|
||||||
|
subject to various criteria. There are a few examples of CoinSelector's:
|
||||||
|
|
||||||
|
- MinIndexCoinSelector
|
||||||
|
|
||||||
|
- MinNumberCoinSelector
|
||||||
|
|
||||||
|
- MaxValueAgeCoinSelector
|
||||||
|
|
||||||
|
- MinPriorityCoinSelector
|
||||||
|
|
||||||
|
For example, if the user wishes to maximize the probability that their
|
||||||
|
transaction is mined quickly, they could use the MaxValueAgeCoinSelector to
|
||||||
|
select high priority coins, then also attach a relatively high fee.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
selector := &coinset.MaxValueAgeCoinSelector{
|
||||||
|
MaxInputs: 10,
|
||||||
|
MinAmountChange: 10000,
|
||||||
|
}
|
||||||
|
selectedCoins, err := selector.CoinSelect(targetAmount + bigFee, unspentCoins)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msgTx := coinset.NewMsgTxWithInputCoins(selectedCoins)
|
||||||
|
...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The user can then create the msgTx.TxOut's as required, then sign the
|
||||||
|
transaction and transmit it to the network.
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Package coinset is licensed under the liberal ISC License.
|
394
coinset/coins.go
Normal file
394
coinset/coins.go
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
package coinset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"errors"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Coin represents a spendable transaction outpoint
|
||||||
|
type Coin interface {
|
||||||
|
Hash() *btcwire.ShaHash
|
||||||
|
Index() uint32
|
||||||
|
Value() int64
|
||||||
|
PkScript() []byte
|
||||||
|
NumConfs() int64
|
||||||
|
ValueAge() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coins represents a set of Coins
|
||||||
|
type Coins interface {
|
||||||
|
Coins() []Coin
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSet is a utility struct for the modifications of a set of
|
||||||
|
// Coins that implements the Coins interface. To create a CoinSet,
|
||||||
|
// you must call NewCoinSet with nil for an empty set or a slice of
|
||||||
|
// coins as the initial contents.
|
||||||
|
//
|
||||||
|
// It is important to note that the all the Coins being added or removed
|
||||||
|
// from a CoinSet must have a constant ValueAge() during the use of
|
||||||
|
// the CoinSet, otherwise the cached values will be incorrect.
|
||||||
|
type CoinSet struct {
|
||||||
|
coinList *list.List
|
||||||
|
totalValue int64
|
||||||
|
totalValueAge int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that CoinSet is a Coins
|
||||||
|
var _ Coins = NewCoinSet(nil)
|
||||||
|
|
||||||
|
// NewCoinSet creates a CoinSet containing the coins provided.
|
||||||
|
// To create an empty CoinSet, you may pass null as the coins input parameter.
|
||||||
|
func NewCoinSet(coins []Coin) *CoinSet {
|
||||||
|
newCoinSet := &CoinSet{
|
||||||
|
coinList: list.New(),
|
||||||
|
totalValue: 0,
|
||||||
|
totalValueAge: 0,
|
||||||
|
}
|
||||||
|
for _, coin := range coins {
|
||||||
|
newCoinSet.PushCoin(coin)
|
||||||
|
}
|
||||||
|
return newCoinSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coins returns a new slice of the coins contained in the set.
|
||||||
|
func (cs *CoinSet) Coins() []Coin {
|
||||||
|
coins := make([]Coin, cs.coinList.Len())
|
||||||
|
for i, e := 0, cs.coinList.Front(); e != nil; i, e = i+1, e.Next() {
|
||||||
|
coins[i] = e.Value.(Coin)
|
||||||
|
}
|
||||||
|
return coins
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalValue returns the total value of the coins in the set.
|
||||||
|
func (cs *CoinSet) TotalValue() (value int64) {
|
||||||
|
return cs.totalValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalValueAge returns the total value * number of confirmations
|
||||||
|
// of the coins in the set.
|
||||||
|
func (cs *CoinSet) TotalValueAge() (valueAge int64) {
|
||||||
|
return cs.totalValueAge
|
||||||
|
}
|
||||||
|
|
||||||
|
// Num returns the number of coins in the set
|
||||||
|
func (cs *CoinSet) Num() int {
|
||||||
|
return cs.coinList.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushCoin adds a coin to the end of the list and updates
|
||||||
|
// the cached value amounts.
|
||||||
|
func (cs *CoinSet) PushCoin(c Coin) {
|
||||||
|
cs.coinList.PushBack(c)
|
||||||
|
cs.totalValue += c.Value()
|
||||||
|
cs.totalValueAge += c.ValueAge()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopCoin removes the last coin on the list and returns it.
|
||||||
|
func (cs *CoinSet) PopCoin() Coin {
|
||||||
|
back := cs.coinList.Back()
|
||||||
|
if back == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cs.removeElement(back)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShiftCoin removes the first coin on the list and returns it.
|
||||||
|
func (cs *CoinSet) ShiftCoin() Coin {
|
||||||
|
front := cs.coinList.Front()
|
||||||
|
if front == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cs.removeElement(front)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeElement updates the cached value amounts in the CoinSet,
|
||||||
|
// removes the element from the list, then returns the Coin that
|
||||||
|
// was removed to the caller.
|
||||||
|
func (cs *CoinSet) removeElement(e *list.Element) Coin {
|
||||||
|
c := e.Value.(Coin)
|
||||||
|
cs.coinList.Remove(e)
|
||||||
|
cs.totalValue -= c.Value()
|
||||||
|
cs.totalValueAge -= c.ValueAge()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgTxWithInputCoins takes the coins in the CoinSet and makes them
|
||||||
|
// the inputs to a new btcwire.MsgTx which is returned.
|
||||||
|
func NewMsgTxWithInputCoins(inputCoins Coins) *btcwire.MsgTx {
|
||||||
|
msgTx := btcwire.NewMsgTx()
|
||||||
|
coins := inputCoins.Coins()
|
||||||
|
msgTx.TxIn = make([]*btcwire.TxIn, len(coins))
|
||||||
|
for i, coin := range coins {
|
||||||
|
msgTx.TxIn[i] = &btcwire.TxIn{
|
||||||
|
PreviousOutpoint: btcwire.OutPoint{
|
||||||
|
Hash: *coin.Hash(),
|
||||||
|
Index: coin.Index(),
|
||||||
|
},
|
||||||
|
SignatureScript: nil,
|
||||||
|
Sequence: btcwire.MaxTxInSequenceNum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msgTx
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCoinsNoSelectionAvailable is returned when a CoinSelector believes there is no
|
||||||
|
// possible combination of coins which can meet the requirements provided to the selector.
|
||||||
|
ErrCoinsNoSelectionAvailable = errors.New("no coin selection possible")
|
||||||
|
)
|
||||||
|
|
||||||
|
// satisfiesTargetValue checks that the totalValue is either exactly the targetValue
|
||||||
|
// or is greater than the targetValue by at least the minChange amount.
|
||||||
|
func satisfiesTargetValue(targetValue, minChange, totalValue int64) bool {
|
||||||
|
return (totalValue == targetValue || totalValue >= targetValue+minChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSelector is an interface that wraps the CoinSelect method.
|
||||||
|
//
|
||||||
|
// CoinSelect will attempt to select a subset of the coins which has at
|
||||||
|
// least the targetValue amount. CoinSelect is not guaranteed to return a
|
||||||
|
// selection of coins even if the total value of coins given is greater
|
||||||
|
// than the target value.
|
||||||
|
//
|
||||||
|
// The exact choice of coins in the subset will be implementation specific.
|
||||||
|
//
|
||||||
|
// It is important to note that the Coins being used as inputs need to have
|
||||||
|
// a constant ValueAge() during the execution of CoinSelect.
|
||||||
|
type CoinSelector interface {
|
||||||
|
CoinSelect(targetValue int64, coins []Coin) (Coins, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinIndexCoinSelector is a CoinSelector that attempts to construct a
|
||||||
|
// selection of coins whose total value is at least targetValue and prefers
|
||||||
|
// any number of lower indexes (as in the ordered array) over higher ones.
|
||||||
|
type MinIndexCoinSelector struct {
|
||||||
|
MaxInputs int
|
||||||
|
MinChangeAmount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSelect will attempt to select coins using the algorithm described
|
||||||
|
// in the MinIndexCoinSelector struct.
|
||||||
|
func (s MinIndexCoinSelector) CoinSelect(targetValue int64, coins []Coin) (Coins, error) {
|
||||||
|
cs := NewCoinSet(nil)
|
||||||
|
for n := 0; n < len(coins) && n < s.MaxInputs; n++ {
|
||||||
|
cs.PushCoin(coins[n])
|
||||||
|
if satisfiesTargetValue(targetValue, s.MinChangeAmount, cs.TotalValue()) {
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrCoinsNoSelectionAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinNumberCoinSelector is a CoinSelector that attempts to construct
|
||||||
|
// a selection of coins whose total value is at least targetValue
|
||||||
|
// that uses as few of the inputs as possible.
|
||||||
|
type MinNumberCoinSelector struct {
|
||||||
|
MaxInputs int
|
||||||
|
MinChangeAmount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSelect will attempt to select coins using the algorithm described
|
||||||
|
// in the MinNumberCoinSelector struct.
|
||||||
|
func (s MinNumberCoinSelector) CoinSelect(targetValue int64, coins []Coin) (Coins, error) {
|
||||||
|
sortedCoins := make([]Coin, 0, len(coins))
|
||||||
|
sortedCoins = append(sortedCoins, coins...)
|
||||||
|
sort.Sort(sort.Reverse(byAmount(sortedCoins)))
|
||||||
|
return (&MinIndexCoinSelector{
|
||||||
|
MaxInputs: s.MaxInputs,
|
||||||
|
MinChangeAmount: s.MinChangeAmount,
|
||||||
|
}).CoinSelect(targetValue, sortedCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxValueAgeCoinSelector is a CoinSelector that attempts to construct
|
||||||
|
// a selection of coins whose total value is at least targetValue
|
||||||
|
// that has as much input value-age as possible.
|
||||||
|
//
|
||||||
|
// This would be useful in the case where you want to maximize
|
||||||
|
// likelihood of the inclusion of your transaction in the next mined
|
||||||
|
// block.
|
||||||
|
type MaxValueAgeCoinSelector struct {
|
||||||
|
MaxInputs int
|
||||||
|
MinChangeAmount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSelect will attempt to select coins using the algorithm described
|
||||||
|
// in the MaxValueAgeSelector struct.
|
||||||
|
func (s MaxValueAgeCoinSelector) CoinSelect(targetValue int64, coins []Coin) (Coins, error) {
|
||||||
|
sortedCoins := make([]Coin, 0, len(coins))
|
||||||
|
sortedCoins = append(sortedCoins, coins...)
|
||||||
|
sort.Sort(sort.Reverse(byValueAge(sortedCoins)))
|
||||||
|
return (&MinIndexCoinSelector{
|
||||||
|
MaxInputs: s.MaxInputs,
|
||||||
|
MinChangeAmount: s.MinChangeAmount,
|
||||||
|
}).CoinSelect(targetValue, sortedCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinPriorityCoinSelector is a CoinSelector that attempts to construct
|
||||||
|
// a selection of coins whose total value is at least targetValue and
|
||||||
|
// whose average value-age per input is greater than MinAvgValueAgePerInput.
|
||||||
|
// If there is change, it must exceed MinChangeAmount to be a valid selection.
|
||||||
|
//
|
||||||
|
// When possible, MinPriorityCoinSelector will attempt to reduce the average
|
||||||
|
// input priority over the threshold, but no guarantees will be made as to
|
||||||
|
// minimality of the selection. The selection below is almost certainly
|
||||||
|
// suboptimal.
|
||||||
|
//
|
||||||
|
type MinPriorityCoinSelector struct {
|
||||||
|
MaxInputs int
|
||||||
|
MinChangeAmount int64
|
||||||
|
MinAvgValueAgePerInput int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoinSelect will attempt to select coins using the algorithm described
|
||||||
|
// in the MinPriorityCoinSelector struct.
|
||||||
|
func (s MinPriorityCoinSelector) CoinSelect(targetValue int64, coins []Coin) (Coins, error) {
|
||||||
|
possibleCoins := make([]Coin, 0, len(coins))
|
||||||
|
possibleCoins = append(possibleCoins, coins...)
|
||||||
|
|
||||||
|
sort.Sort(byValueAge(possibleCoins))
|
||||||
|
|
||||||
|
// find the first coin with sufficient valueAge
|
||||||
|
cutoffIndex := -1
|
||||||
|
for i := 0; i < len(possibleCoins); i++ {
|
||||||
|
if possibleCoins[i].ValueAge() >= s.MinAvgValueAgePerInput {
|
||||||
|
cutoffIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cutoffIndex < 0 {
|
||||||
|
return nil, ErrCoinsNoSelectionAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// create sets of input coins that will obey minimum average valueAge
|
||||||
|
for i := cutoffIndex; i < len(possibleCoins); i++ {
|
||||||
|
possibleHighCoins := possibleCoins[cutoffIndex : i+1]
|
||||||
|
|
||||||
|
// choose a set of high-enough valueAge coins
|
||||||
|
highSelect, err := (&MinNumberCoinSelector{
|
||||||
|
MaxInputs: s.MaxInputs,
|
||||||
|
MinChangeAmount: s.MinChangeAmount,
|
||||||
|
}).CoinSelect(targetValue, possibleHighCoins)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// attempt to add available low priority to make a solution
|
||||||
|
|
||||||
|
for numLow := 1; numLow <= cutoffIndex && numLow+(i-cutoffIndex) <= s.MaxInputs; numLow++ {
|
||||||
|
allHigh := NewCoinSet(possibleCoins[cutoffIndex : i+1])
|
||||||
|
newTargetValue := targetValue - allHigh.TotalValue()
|
||||||
|
newMaxInputs := allHigh.Num() + numLow
|
||||||
|
if newMaxInputs > numLow {
|
||||||
|
newMaxInputs = numLow
|
||||||
|
}
|
||||||
|
newMinAvgValueAge := ((s.MinAvgValueAgePerInput * int64(allHigh.Num()+numLow)) - allHigh.TotalValueAge()) / int64(numLow)
|
||||||
|
|
||||||
|
// find the minimum priority that can be added to set
|
||||||
|
lowSelect, err := (&MinPriorityCoinSelector{
|
||||||
|
MaxInputs: newMaxInputs,
|
||||||
|
MinChangeAmount: s.MinChangeAmount,
|
||||||
|
MinAvgValueAgePerInput: newMinAvgValueAge,
|
||||||
|
}).CoinSelect(newTargetValue, possibleCoins[0:cutoffIndex])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, coin := range lowSelect.Coins() {
|
||||||
|
allHigh.PushCoin(coin)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allHigh, nil
|
||||||
|
}
|
||||||
|
// oh well, couldn't fix, try to add more high priority to the set.
|
||||||
|
} else {
|
||||||
|
extendedCoins := NewCoinSet(highSelect.Coins())
|
||||||
|
|
||||||
|
// attempt to lower priority towards target with lowest ones first
|
||||||
|
for n := 0; n < cutoffIndex; n++ {
|
||||||
|
if extendedCoins.Num() >= s.MaxInputs {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if possibleCoins[n].ValueAge() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
extendedCoins.PushCoin(possibleCoins[n])
|
||||||
|
if extendedCoins.TotalValueAge()/int64(extendedCoins.Num()) < s.MinAvgValueAgePerInput {
|
||||||
|
extendedCoins.PopCoin()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extendedCoins, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrCoinsNoSelectionAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
type byValueAge []Coin
|
||||||
|
|
||||||
|
func (a byValueAge) Len() int { return len(a) }
|
||||||
|
func (a byValueAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byValueAge) Less(i, j int) bool { return a[i].ValueAge() < a[j].ValueAge() }
|
||||||
|
|
||||||
|
type byAmount []Coin
|
||||||
|
|
||||||
|
func (a byAmount) Len() int { return len(a) }
|
||||||
|
func (a byAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byAmount) Less(i, j int) bool { return a[i].Value() < a[j].Value() }
|
||||||
|
|
||||||
|
// SimpleCoin defines a concrete instance of Coin that is backed by a
|
||||||
|
// btcutil.Tx, a specific outpoint index, and the number of confirmations
|
||||||
|
// that transaction has had.
|
||||||
|
type SimpleCoin struct {
|
||||||
|
Tx *btcutil.Tx
|
||||||
|
TxIndex uint32
|
||||||
|
TxNumConfs int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that SimpleCoin is a Coin
|
||||||
|
var _ Coin = &SimpleCoin{}
|
||||||
|
|
||||||
|
// Hash returns the hash value of the transaction on which the Coin is an output
|
||||||
|
func (c *SimpleCoin) Hash() *btcwire.ShaHash {
|
||||||
|
return c.Tx.Sha()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the index of the output on the transaction which the Coin represents
|
||||||
|
func (c *SimpleCoin) Index() uint32 {
|
||||||
|
return c.TxIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// txOut returns the TxOut of the transaction the Coin represents
|
||||||
|
func (c *SimpleCoin) txOut() *btcwire.TxOut {
|
||||||
|
return c.Tx.MsgTx().TxOut[c.TxIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the value of the Coin
|
||||||
|
func (c *SimpleCoin) Value() int64 {
|
||||||
|
return c.txOut().Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// PkScript returns the outpoint script of the Coin.
|
||||||
|
//
|
||||||
|
// This can be used to determine what type of script the Coin uses
|
||||||
|
// and extract standard addresses if possible using
|
||||||
|
// btcscript.ExtractPkScriptAddrs for example.
|
||||||
|
func (c *SimpleCoin) PkScript() []byte {
|
||||||
|
return c.txOut().PkScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumConfs returns the number of confirmations that the transaction the Coin references
|
||||||
|
// has had.
|
||||||
|
func (c *SimpleCoin) NumConfs() int64 {
|
||||||
|
return c.TxNumConfs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueAge returns the product of the value and the number of confirmations. This is
|
||||||
|
// used as an input to calculate the priority of the transaction.
|
||||||
|
func (c *SimpleCoin) ValueAge() int64 {
|
||||||
|
return c.TxNumConfs * c.Value()
|
||||||
|
}
|
252
coinset/coins_test.go
Normal file
252
coinset/coins_test.go
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package coinset_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcutil/coinset"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/conformal/fastsha256"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCoin struct {
|
||||||
|
TxHash *btcwire.ShaHash
|
||||||
|
TxIndex uint32
|
||||||
|
TxValue int64
|
||||||
|
TxNumConfs int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TestCoin) Hash() *btcwire.ShaHash { return c.TxHash }
|
||||||
|
func (c *TestCoin) Index() uint32 { return c.TxIndex }
|
||||||
|
func (c *TestCoin) Value() int64 { return c.TxValue }
|
||||||
|
func (c *TestCoin) PkScript() []byte { return nil }
|
||||||
|
func (c *TestCoin) NumConfs() int64 { return c.TxNumConfs }
|
||||||
|
func (c *TestCoin) ValueAge() int64 { return c.TxValue * c.TxNumConfs }
|
||||||
|
|
||||||
|
func NewCoin(index, value, numConfs int64) coinset.Coin {
|
||||||
|
h := fastsha256.New()
|
||||||
|
h.Write([]byte(fmt.Sprintf("%d", index)))
|
||||||
|
hash, _ := btcwire.NewShaHash(h.Sum(nil))
|
||||||
|
c := &TestCoin{
|
||||||
|
TxHash: hash,
|
||||||
|
TxIndex: 0,
|
||||||
|
TxValue: value,
|
||||||
|
TxNumConfs: numConfs,
|
||||||
|
}
|
||||||
|
return coinset.Coin(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type coinSelectTest struct {
|
||||||
|
selector coinset.CoinSelector
|
||||||
|
inputCoins []coinset.Coin
|
||||||
|
targetValue int64
|
||||||
|
expectedCoins []coinset.Coin
|
||||||
|
expectedError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCoinSelector(tests []coinSelectTest, t *testing.T) {
|
||||||
|
for testIndex, test := range tests {
|
||||||
|
cs, err := test.selector.CoinSelect(test.targetValue, test.inputCoins)
|
||||||
|
if err != test.expectedError {
|
||||||
|
t.Errorf("[%d] expected a different error: got=%v, expected=%v", testIndex, err, test.expectedError)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.expectedCoins != nil {
|
||||||
|
if cs == nil {
|
||||||
|
t.Errorf("[%d] expected non-nil coinset", testIndex)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
coins := cs.Coins()
|
||||||
|
if len(coins) != len(test.expectedCoins) {
|
||||||
|
t.Errorf("[%d] expected different number of coins: got=%d, expected=%d", testIndex, len(coins), len(test.expectedCoins))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for n := 0; n < len(test.expectedCoins); n++ {
|
||||||
|
if coins[n] != test.expectedCoins[n] {
|
||||||
|
t.Errorf("[%d] expected different coins at coin index %d: got=%#v, expected=%#v", testIndex, n, coins[n], test.expectedCoins[n])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coinSet := coinset.NewCoinSet(coins)
|
||||||
|
if coinSet.TotalValue() < test.targetValue {
|
||||||
|
t.Errorf("[%d] targetValue not satistifed", testIndex)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var coins = []coinset.Coin{
|
||||||
|
NewCoin(1, 100000000, 1),
|
||||||
|
NewCoin(2, 10000000, 20),
|
||||||
|
NewCoin(3, 50000000, 0),
|
||||||
|
NewCoin(4, 25000000, 6),
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoinSet(t *testing.T) {
|
||||||
|
cs := coinset.NewCoinSet(nil)
|
||||||
|
if cs.PopCoin() != nil {
|
||||||
|
t.Error("Expected popCoin of empty to be nil")
|
||||||
|
}
|
||||||
|
if cs.ShiftCoin() != nil {
|
||||||
|
t.Error("Expected shiftCoin of empty to be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.PushCoin(coins[0])
|
||||||
|
cs.PushCoin(coins[1])
|
||||||
|
cs.PushCoin(coins[2])
|
||||||
|
if cs.PopCoin() != coins[2] {
|
||||||
|
t.Error("Expected third coin")
|
||||||
|
}
|
||||||
|
if cs.ShiftCoin() != coins[0] {
|
||||||
|
t.Error("Expected first coin")
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx := coinset.NewMsgTxWithInputCoins(cs)
|
||||||
|
if len(mtx.TxIn) != 1 {
|
||||||
|
t.Errorf("Expected only 1 TxIn, got %d", len(mtx.TxIn))
|
||||||
|
}
|
||||||
|
op := mtx.TxIn[0].PreviousOutpoint
|
||||||
|
if !op.Hash.IsEqual(coins[1].Hash()) || op.Index != coins[1].Index() {
|
||||||
|
t.Errorf("Expected the second coin to be added as input to mtx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minIndexSelectors = []coinset.MinIndexCoinSelector{
|
||||||
|
coinset.MinIndexCoinSelector{MaxInputs: 10, MinChangeAmount: 10000},
|
||||||
|
coinset.MinIndexCoinSelector{MaxInputs: 2, MinChangeAmount: 10000},
|
||||||
|
}
|
||||||
|
|
||||||
|
var minIndexTests = []coinSelectTest{
|
||||||
|
{minIndexSelectors[0], coins, coins[0].Value() - minIndexSelectors[0].MinChangeAmount, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minIndexSelectors[0], coins, coins[0].Value() - minIndexSelectors[0].MinChangeAmount + 1, []coinset.Coin{coins[0], coins[1]}, nil},
|
||||||
|
{minIndexSelectors[0], coins, 100000000, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minIndexSelectors[0], coins, 110000000, []coinset.Coin{coins[0], coins[1]}, nil},
|
||||||
|
{minIndexSelectors[0], coins, 140000000, []coinset.Coin{coins[0], coins[1], coins[2]}, nil},
|
||||||
|
{minIndexSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minIndexSelectors[1], coins, 10000000, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minIndexSelectors[1], coins, 110000000, []coinset.Coin{coins[0], coins[1]}, nil},
|
||||||
|
{minIndexSelectors[1], coins, 140000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinIndexSelector(t *testing.T) {
|
||||||
|
testCoinSelector(minIndexTests, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var minNumberSelectors = []coinset.MinNumberCoinSelector{
|
||||||
|
coinset.MinNumberCoinSelector{MaxInputs: 10, MinChangeAmount: 10000},
|
||||||
|
coinset.MinNumberCoinSelector{MaxInputs: 2, MinChangeAmount: 10000},
|
||||||
|
}
|
||||||
|
|
||||||
|
var minNumberTests = []coinSelectTest{
|
||||||
|
{minNumberSelectors[0], coins, coins[0].Value() - minNumberSelectors[0].MinChangeAmount, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, coins[0].Value() - minNumberSelectors[0].MinChangeAmount + 1, []coinset.Coin{coins[0], coins[2]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, 100000000, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, 110000000, []coinset.Coin{coins[0], coins[2]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, 160000000, []coinset.Coin{coins[0], coins[2], coins[3]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, 184990000, []coinset.Coin{coins[0], coins[2], coins[3], coins[1]}, nil},
|
||||||
|
{minNumberSelectors[0], coins, 184990001, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minNumberSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minNumberSelectors[1], coins, 10000000, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minNumberSelectors[1], coins, 110000000, []coinset.Coin{coins[0], coins[2]}, nil},
|
||||||
|
{minNumberSelectors[1], coins, 140000000, []coinset.Coin{coins[0], coins[2]}, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinNumberSelector(t *testing.T) {
|
||||||
|
testCoinSelector(minNumberTests, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxValueAgeSelectors = []coinset.MaxValueAgeCoinSelector{
|
||||||
|
coinset.MaxValueAgeCoinSelector{MaxInputs: 10, MinChangeAmount: 10000},
|
||||||
|
coinset.MaxValueAgeCoinSelector{MaxInputs: 2, MinChangeAmount: 10000},
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxValueAgeTests = []coinSelectTest{
|
||||||
|
{maxValueAgeSelectors[0], coins, 100000, []coinset.Coin{coins[1]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 10000000, []coinset.Coin{coins[1]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 10000001, []coinset.Coin{coins[1], coins[3]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 35000000, []coinset.Coin{coins[1], coins[3]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 135000000, []coinset.Coin{coins[1], coins[3], coins[0]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 185000000, []coinset.Coin{coins[1], coins[3], coins[0], coins[2]}, nil},
|
||||||
|
{maxValueAgeSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{maxValueAgeSelectors[1], coins, 40000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{maxValueAgeSelectors[1], coins, 35000000, []coinset.Coin{coins[1], coins[3]}, nil},
|
||||||
|
{maxValueAgeSelectors[1], coins, 34990001, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxValueAgeSelector(t *testing.T) {
|
||||||
|
testCoinSelector(maxValueAgeTests, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var minPrioritySelectors = []coinset.MinPriorityCoinSelector{
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 100000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 200000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 150000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 03, MinChangeAmount: 10000, MinAvgValueAgePerInput: 150000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 1000000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 175000000},
|
||||||
|
coinset.MinPriorityCoinSelector{MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 125000000},
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectedCoins = []coinset.Coin{coins[0], coins[1], coins[3]}
|
||||||
|
|
||||||
|
var minPriorityTests = []coinSelectTest{
|
||||||
|
{minPrioritySelectors[0], connectedCoins, 100000000, []coinset.Coin{coins[0]}, nil},
|
||||||
|
{minPrioritySelectors[0], connectedCoins, 125000000, []coinset.Coin{coins[0], coins[3]}, nil},
|
||||||
|
{minPrioritySelectors[0], connectedCoins, 135000000, []coinset.Coin{coins[0], coins[3], coins[1]}, nil},
|
||||||
|
{minPrioritySelectors[0], connectedCoins, 140000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minPrioritySelectors[1], connectedCoins, 100000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minPrioritySelectors[1], connectedCoins, 10000000, []coinset.Coin{coins[1]}, nil},
|
||||||
|
{minPrioritySelectors[1], connectedCoins, 100000000, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minPrioritySelectors[2], connectedCoins, 11000000, []coinset.Coin{coins[3]}, nil},
|
||||||
|
{minPrioritySelectors[2], connectedCoins, 25000001, []coinset.Coin{coins[3], coins[1]}, nil},
|
||||||
|
{minPrioritySelectors[3], connectedCoins, 25000001, []coinset.Coin{coins[3], coins[1], coins[0]}, nil},
|
||||||
|
{minPrioritySelectors[3], connectedCoins, 100000000, []coinset.Coin{coins[3], coins[1], coins[0]}, nil},
|
||||||
|
{minPrioritySelectors[3], []coinset.Coin{coins[1], coins[2]}, 10000000, []coinset.Coin{coins[1]}, nil},
|
||||||
|
{minPrioritySelectors[4], connectedCoins, 1, nil, coinset.ErrCoinsNoSelectionAvailable},
|
||||||
|
{minPrioritySelectors[5], connectedCoins, 20000000, []coinset.Coin{coins[1], coins[3]}, nil},
|
||||||
|
{minPrioritySelectors[6], connectedCoins, 25000000, []coinset.Coin{coins[3], coins[0]}, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinPrioritySelector(t *testing.T) {
|
||||||
|
testCoinSelector(minPriorityTests, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// should be two outpoints, with 1st one having 0.035BTC value.
|
||||||
|
testSimpleCoinTxHash = "9b5965c86de51d5dc824e179a05cf232db78c80ae86ca9d7cb2a655b5e19c1e2"
|
||||||
|
testSimpleCoinTxHex = "0100000001a214a110f79e4abe073865ea5b3745c6e82c913bad44be70652804a5e4003b0a010000008c493046022100edd18a69664efa57264be207100c203e6cade1888cbb88a0ad748548256bb2f0022100f1027dc2e6c7f248d78af1dd90027b5b7d8ec563bb62aa85d4e74d6376f3868c0141048f3757b65ed301abd1b0e8942d1ab5b50594d3314cff0299f300c696376a0a9bf72e74710a8af7a5372d4af4bb519e2701a094ef48c8e48e3b65b28502452dceffffffff02e0673500000000001976a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ace86ef102000000001976a914ac3f995655e81b875b38b64351d6f896ddbfc68588ac00000000"
|
||||||
|
testSimpleCoinTxValue0 = int64(3500000)
|
||||||
|
testSimpleCoinTxPkScript0Hex = "76a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ac"
|
||||||
|
testSimpleCoinTxPkScript0Bytes, _ = hex.DecodeString(testSimpleCoinTxPkScript0Hex)
|
||||||
|
testSimpleCoinTxBytes, _ = hex.DecodeString(testSimpleCoinTxHex)
|
||||||
|
testSimpleCoinTx, _ = btcutil.NewTxFromBytes(testSimpleCoinTxBytes)
|
||||||
|
testSimpleCoin = &coinset.SimpleCoin{
|
||||||
|
Tx: testSimpleCoinTx,
|
||||||
|
TxIndex: 0,
|
||||||
|
TxNumConfs: 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleCoin(t *testing.T) {
|
||||||
|
if testSimpleCoin.Hash().String() != testSimpleCoinTxHash {
|
||||||
|
t.Error("Different value for tx hash than expected")
|
||||||
|
}
|
||||||
|
if testSimpleCoin.Index() != 0 {
|
||||||
|
t.Error("Different value for index of outpoint than expected")
|
||||||
|
}
|
||||||
|
if testSimpleCoin.Value() != testSimpleCoinTxValue0 {
|
||||||
|
t.Error("Different value of coin value than expected")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(testSimpleCoin.PkScript(), testSimpleCoinTxPkScript0Bytes) {
|
||||||
|
t.Error("Different value of coin pkScript than expected")
|
||||||
|
}
|
||||||
|
if testSimpleCoin.NumConfs() != 1 {
|
||||||
|
t.Error("Differet value of num confs than expected")
|
||||||
|
}
|
||||||
|
if testSimpleCoin.ValueAge() != testSimpleCoinTxValue0 {
|
||||||
|
t.Error("Different value of coin value * age than expected")
|
||||||
|
}
|
||||||
|
}
|
17
coinset/cov_report.sh
Normal file
17
coinset/cov_report.sh
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
type gocov >/dev/null 2>&1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gocov test | gocov report
|
31
coinset/test_coverage.txt
Normal file
31
coinset/test_coverage.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go MinPriorityCoinSelector.CoinSelect 100.00% (39/39)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go NewMsgTxWithInputCoins 100.00% (6/6)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go MinIndexCoinSelector.CoinSelect 100.00% (6/6)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.removeElement 100.00% (5/5)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go NewCoinSet 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.Coins 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.PopCoin 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.ShiftCoin 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go MinNumberCoinSelector.CoinSelect 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go MaxValueAgeCoinSelector.CoinSelect 100.00% (4/4)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.PushCoin 100.00% (3/3)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.TotalValueAge 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.NumConfs 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.ValueAge 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.TotalValue 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byValueAge.Len 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byValueAge.Swap 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byValueAge.Less 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byAmount.Len 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byAmount.Swap 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go byAmount.Less 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Hash 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Index 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.txOut 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Value 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go SimpleCoin.PkScript 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go CoinSet.Num 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset/coins.go satisfiesTargetValue 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/coinset ---------------------------------- 100.00% (100/100)
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
|
|
||||||
github.com/conformal/btcutil/base58.go Base58Decode 100.00% (20/20)
|
github.com/conformal/btcutil/base58.go Base58Decode 100.00% (20/20)
|
||||||
|
github.com/conformal/btcutil/address.go DecodeAddr 100.00% (18/18)
|
||||||
github.com/conformal/btcutil/base58.go Base58Encode 100.00% (15/15)
|
github.com/conformal/btcutil/base58.go Base58Encode 100.00% (15/15)
|
||||||
github.com/conformal/btcutil/addrconvs.go DecodeAddress 100.00% (14/14)
|
github.com/conformal/btcutil/addrconvs.go DecodeAddress 100.00% (14/14)
|
||||||
github.com/conformal/btcutil/block.go Block.Tx 100.00% (12/12)
|
github.com/conformal/btcutil/block.go Block.Tx 100.00% (12/12)
|
||||||
github.com/conformal/btcutil/block.go Block.Transactions 100.00% (11/11)
|
github.com/conformal/btcutil/block.go Block.Transactions 100.00% (11/11)
|
||||||
github.com/conformal/btcutil/block.go Block.TxShas 100.00% (10/10)
|
github.com/conformal/btcutil/block.go Block.TxShas 100.00% (10/10)
|
||||||
github.com/conformal/btcutil/address.go encodeAddress 100.00% (9/9)
|
github.com/conformal/btcutil/address.go encodeAddress 100.00% (9/9)
|
||||||
github.com/conformal/btcutil/addrconvs.go EncodeAddress 100.00% (8/8)
|
|
||||||
github.com/conformal/btcutil/addrconvs.go EncodeScriptHash 100.00% (8/8)
|
github.com/conformal/btcutil/addrconvs.go EncodeScriptHash 100.00% (8/8)
|
||||||
github.com/conformal/btcutil/addrconvs.go encodeHashWithNetId 100.00% (7/7)
|
github.com/conformal/btcutil/addrconvs.go EncodeAddress 100.00% (8/8)
|
||||||
github.com/conformal/btcutil/tx.go NewTxFromBytes 100.00% (7/7)
|
github.com/conformal/btcutil/tx.go NewTxFromBytes 100.00% (7/7)
|
||||||
github.com/conformal/btcutil/block.go NewBlockFromBytes 100.00% (7/7)
|
github.com/conformal/btcutil/block.go NewBlockFromBytes 100.00% (7/7)
|
||||||
|
github.com/conformal/btcutil/addrconvs.go encodeHashWithNetId 100.00% (7/7)
|
||||||
github.com/conformal/btcutil/block.go Block.Sha 100.00% (5/5)
|
github.com/conformal/btcutil/block.go Block.Sha 100.00% (5/5)
|
||||||
github.com/conformal/btcutil/tx.go Tx.Sha 100.00% (5/5)
|
github.com/conformal/btcutil/tx.go Tx.Sha 100.00% (5/5)
|
||||||
github.com/conformal/btcutil/block.go Block.TxSha 100.00% (4/4)
|
github.com/conformal/btcutil/block.go Block.TxSha 100.00% (4/4)
|
||||||
github.com/conformal/btcutil/hash160.go calcHash 100.00% (2/2)
|
github.com/conformal/btcutil/hash160.go calcHash 100.00% (2/2)
|
||||||
github.com/conformal/btcutil/address.go NewAddressScriptHash 100.00% (2/2)
|
github.com/conformal/btcutil/address.go NewAddressScriptHash 100.00% (2/2)
|
||||||
github.com/conformal/btcutil/block.go NewBlock 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/tx.go NewTx 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.String 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/tx.go Tx.SetIndex 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go Block.MsgBlock 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go NewBlockFromBlockAndBytes 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.ScriptAddress 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.ScriptAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.ScriptAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/block.go NewBlock 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go Block.SetHeight 100.00% (1/1)
|
github.com/conformal/btcutil/block.go Block.SetHeight 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.EncodeAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.ScriptAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.ScriptAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/tx.go Tx.Index 100.00% (1/1)
|
github.com/conformal/btcutil/tx.go NewTx 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.String 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.String 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/tx.go Tx.MsgTx 100.00% (1/1)
|
github.com/conformal/btcutil/tx.go Tx.SetIndex 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/tx.go Tx.Index 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressScriptHash.ScriptAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/hash160.go Hash160 100.00% (1/1)
|
github.com/conformal/btcutil/hash160.go Hash160 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.EncodeAddress 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go NewBlockFromBlockAndBytes 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/block.go OutOfRangeError.Error 100.00% (1/1)
|
github.com/conformal/btcutil/block.go OutOfRangeError.Error 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go DecodeAddr 95.65% (22/23)
|
github.com/conformal/btcutil/block.go Block.MsgBlock 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/tx.go Tx.MsgTx 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/appdata.go appDataDir 92.00% (23/25)
|
github.com/conformal/btcutil/appdata.go appDataDir 92.00% (23/25)
|
||||||
github.com/conformal/btcutil/address.go NewAddressPubKeyHash 91.67% (11/12)
|
|
||||||
github.com/conformal/btcutil/address.go NewAddressScriptHashFromHash 91.67% (11/12)
|
github.com/conformal/btcutil/address.go NewAddressScriptHashFromHash 91.67% (11/12)
|
||||||
|
github.com/conformal/btcutil/address.go NewAddressPubKeyHash 91.67% (11/12)
|
||||||
github.com/conformal/btcutil/addrconvs.go EncodePrivateKey 90.91% (20/22)
|
github.com/conformal/btcutil/addrconvs.go EncodePrivateKey 90.91% (20/22)
|
||||||
github.com/conformal/btcutil/block.go Block.Bytes 88.89% (8/9)
|
|
||||||
github.com/conformal/btcutil/block.go Block.TxLoc 88.89% (8/9)
|
github.com/conformal/btcutil/block.go Block.TxLoc 88.89% (8/9)
|
||||||
|
github.com/conformal/btcutil/block.go Block.Bytes 88.89% (8/9)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.serialize 85.71% (6/7)
|
github.com/conformal/btcutil/address.go AddressPubKey.serialize 85.71% (6/7)
|
||||||
|
github.com/conformal/btcutil/addrconvs.go DecodePrivateKey 83.33% (20/24)
|
||||||
github.com/conformal/btcutil/address.go NewAddressPubKey 83.33% (15/18)
|
github.com/conformal/btcutil/address.go NewAddressPubKey 83.33% (15/18)
|
||||||
github.com/conformal/btcutil/address.go checkBitcoinNet 83.33% (5/6)
|
github.com/conformal/btcutil/address.go checkBitcoinNet 83.33% (5/6)
|
||||||
github.com/conformal/btcutil/addrconvs.go DecodePrivateKey 82.61% (19/23)
|
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.IsForNet 60.00% (3/5)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.IsForNet 60.00% (3/5)
|
github.com/conformal/btcutil/address.go AddressPubKey.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.IsForNet 60.00% (3/5)
|
github.com/conformal/btcutil/address.go AddressScriptHash.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/certgen.go NewTLSCertPair 0.00% (0/50)
|
github.com/conformal/btcutil/certgen.go NewTLSCertPair 0.00% (0/50)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.AddressPubKeyHash 0.00% (0/3)
|
github.com/conformal/btcutil/address.go AddressPubKey.AddressPubKeyHash 0.00% (0/3)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.Format 0.00% (0/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 0.00% (0/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.SetFormat 0.00% (0/1)
|
|
||||||
github.com/conformal/btcutil/appdata.go AppDataDir 0.00% (0/1)
|
github.com/conformal/btcutil/appdata.go AppDataDir 0.00% (0/1)
|
||||||
github.com/conformal/btcutil ------------------------------- 80.15% (323/403)
|
github.com/conformal/btcutil/address.go AddressPubKey.SetFormat 0.00% (0/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressPubKey.Format 0.00% (0/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressScriptHash.String 0.00% (0/1)
|
||||||
|
github.com/conformal/btcutil ------------------------------- 79.70% (318/399)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue