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/address.go DecodeAddr 100.00% (18/18)
|
||||
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/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.TxShas 100.00% (10/10)
|
||||
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 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/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/tx.go Tx.Sha 100.00% (5/5)
|
||||
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/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.EncodeAddress 100.00% (1/1)
|
||||
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
|
||||
github.com/conformal/btcutil/block.go NewBlock 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.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/tx.go Tx.MsgTx 100.00% (1/1)
|
||||
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
|
||||
github.com/conformal/btcutil/tx.go Tx.SetIndex 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/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/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/address.go NewAddressPubKeyHash 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/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.Bytes 88.89% (8/9)
|
||||
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 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 AddressPubKey.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/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 AddressPubKey.SetFormat 0.00% (0/1)
|
||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 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