diff --git a/.travis.yml b/.travis.yml index f65821e926..5063496735 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ cache: - $GOPATH/github.com/golang - $GOPATH/gopkg.in/alecthomas go: - - "1.11.x" + - "1.13.x" sudo: false install: - export PATH=$PATH:$PWD/linux-amd64/ diff --git a/README.md b/README.md index 766e19e505..c7ca97fdc7 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,38 @@ -ltcd -==== +# ltcd [![Build Status](https://travis-ci.org/ltcsuite/ltcd.png?branch=master)](https://travis-ci.org/ltcsuite/ltcd) ltcd is an alternative full node bitcoin implementation written in Go (golang). -This project is currently under active development and is in a Beta state. It +This project is currently under active development and is in a Beta state. It is extremely stable and has been in production use since October 2013. It properly downloads, validates, and serves the block chain using the exact -rules (including consensus bugs) for block acceptance as Bitcoin Core. We have -taken great care to avoid ltcd causing a fork to the block chain. It includes a +rules (including consensus bugs) for block acceptance as Bitcoin Core. We have +taken great care to avoid ltcd causing a fork to the block chain. It includes a full block validation testing framework which contains all of the 'official' block acceptance tests (and some additional ones) that is run on every pull -request to help ensure it properly follows consensus. Also, it passes all of +request to help ensure it properly follows consensus. Also, it passes all of the JSON test data in the Bitcoin Core code. It also properly relays newly mined blocks, maintains a transaction pool, and -relays individual transactions that have not yet made it into a block. It +relays individual transactions that have not yet made it into a block. It ensures all individual transactions admitted to the pool follow the rules required by the block chain and also includes more strict checks which filter transactions based on miner requirements ("standard" transactions). -One key difference between ltcd and Bitcoin Core is that ltcd does *NOT* include -wallet functionality and this was a very intentional design decision. See the +One key difference between ltcd and Bitcoin Core is that ltcd does _NOT_ include +wallet functionality and this was a very intentional design decision. See the blog entry [here](https://blog.conformal.com/ltcd-not-your-moms-bitcoin-daemon) -for more details. This means you can't actually make or receive payments -directly with ltcd. That functionality is provided by the +for more details. This means you can't actually make or receive payments +directly with ltcd. That functionality is provided by the [btcwallet](https://github.com/btcsuite/btcwallet) and [Paymetheus](https://github.com/btcsuite/Paymetheus) (Windows-only) projects which are both under active development. ## Requirements -[Go](http://golang.org) 1.11 or newer. +[Go](http://golang.org) 1.12 or newer. ## Installation @@ -53,9 +52,9 @@ $ go version $ go env GOROOT GOPATH ``` -NOTE: The `GOROOT` and `GOPATH` above must not be the same path. It is +NOTE: The `GOROOT` and `GOPATH` above must not be the same path. It is recommended that `GOPATH` is set to a directory in your home directory such as -`~/goprojects` to avoid write permission issues. It is also recommended to add +`~/goprojects` to avoid write permission issues. It is also recommended to add `$GOPATH/bin` to your `PATH` at this point. - Run the following commands to obtain ltcd, all dependencies, and install it: @@ -65,7 +64,7 @@ $ cd $GOPATH/src/github.com/ltcsuite/ltcd $ GO111MODULE=on go install -v . ./cmd/... ``` -- ltcd (and utilities) will now be installed in ```$GOPATH/bin```. If you did +- ltcd (and utilities) will now be installed in `$GOPATH/bin`. If you did not already add the bin directory to your system path during Go installation, we recommend you do so now. @@ -114,27 +113,14 @@ is used for this project. ## Documentation -The documentation is a work-in-progress. It is located in the [docs](https://github.com/ltcsuite/ltcd/tree/master/docs) folder. +The documentation is a work-in-progress. It is located in the [docs](https://github.com/ltcsuite/ltcd/tree/master/docs) folder. -## GPG Verification Key +## Release Verification -All official release tags are signed by Conformal so users can ensure the code -has not been tampered with and is coming from the ltcsuite developers. To -verify the signature perform the following: - -- Download the Conformal public key: - https://raw.githubusercontent.com/ltcsuite/ltcd/master/release/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 - ``` +Please see our [documentation on the current build/verification +process](https://github.com/ltcsuite/ltcd/tree/master/release) for all our +releases for information on how to verify the integrity of published releases +using our reproducible build system. ## License diff --git a/btcec/bench_test.go b/btcec/bench_test.go index bebd886f1c..7ccd78cfb9 100644 --- a/btcec/bench_test.go +++ b/btcec/bench_test.go @@ -4,7 +4,10 @@ package btcec -import "testing" +import ( + "encoding/hex" + "testing" +) // BenchmarkAddJacobian benchmarks the secp256k1 curve addJacobian function with // Z values of 1 so that the associated optimizations are used. @@ -121,3 +124,22 @@ func BenchmarkFieldNormalize(b *testing.B) { f.Normalize() } } + +// BenchmarkParseCompressedPubKey benchmarks how long it takes to decompress and +// validate a compressed public key from a byte array. +func BenchmarkParseCompressedPubKey(b *testing.B) { + rawPk, _ := hex.DecodeString("0234f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6") + + var ( + pk *PublicKey + err error + ) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + pk, err = ParsePubKey(rawPk, S256()) + } + _ = pk + _ = err +} diff --git a/btcec/btcec.go b/btcec/btcec.go index 5e7ce875fd..de93a255a4 100644 --- a/btcec/btcec.go +++ b/btcec/btcec.go @@ -36,10 +36,17 @@ var ( // interface from crypto/elliptic. type KoblitzCurve struct { *elliptic.CurveParams - q *big.Int + + // q is the value (P+1)/4 used to compute the square root of field + // elements. + q *big.Int + H int // cofactor of the curve. halfOrder *big.Int // half the order N + // fieldB is the constant B of the curve as a fieldVal. + fieldB *fieldVal + // byteSize is simply the bit size / 8 and is provided for convenience // since it is calculated repeatedly. byteSize int @@ -879,12 +886,22 @@ func (curve *KoblitzCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { return curve.fieldJacobianToBigAffine(qx, qy, qz) } -// QPlus1Div4 returns the Q+1/4 constant for the curve for use in calculating -// square roots via exponention. +// QPlus1Div4 returns the (P+1)/4 constant for the curve for use in calculating +// square roots via exponentiation. +// +// DEPRECATED: The actual value returned is (P+1)/4, where as the original +// method name implies that this value is (((P+1)/4)+1)/4. This method is kept +// to maintain backwards compatibility of the API. Use Q() instead. func (curve *KoblitzCurve) QPlus1Div4() *big.Int { return curve.q } +// Q returns the (P+1)/4 constant for the curve for use in calculating square +// roots via exponentiation. +func (curve *KoblitzCurve) Q() *big.Int { + return curve.q +} + var initonce sync.Once var secp256k1 KoblitzCurve @@ -917,6 +934,7 @@ func initS256() { big.NewInt(1)), big.NewInt(4)) secp256k1.H = 1 secp256k1.halfOrder = new(big.Int).Rsh(secp256k1.N, 1) + secp256k1.fieldB = new(fieldVal).SetByteSlice(secp256k1.B.Bytes()) // Provided for convenience since this gets computed repeatedly. secp256k1.byteSize = secp256k1.BitSize / 8 diff --git a/btcec/field.go b/btcec/field.go index 0f2be74c0c..c2bb84b3fe 100644 --- a/btcec/field.go +++ b/btcec/field.go @@ -102,6 +102,20 @@ const ( fieldPrimeWordOne = 0x3ffffbf ) +var ( + // fieldQBytes is the value Q = (P+1)/4 for the secp256k1 prime P. This + // value is used to efficiently compute the square root of values in the + // field via exponentiation. The value of Q in hex is: + // + // Q = 3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c + fieldQBytes = []byte{ + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0x0c, + } +) + // fieldVal implements optimized fixed-precision arithmetic over the // secp256k1 finite field. This means all arithmetic is performed modulo // 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f. It @@ -1221,3 +1235,118 @@ func (f *fieldVal) Inverse() *fieldVal { f.Square().Square().Square().Square().Square() // f = a^(2^256 - 4294968320) return f.Mul(&a45) // f = a^(2^256 - 4294968275) = a^(p-2) } + +// SqrtVal computes the square root of x modulo the curve's prime, and stores +// the result in f. The square root is computed via exponentiation of x by the +// value Q = (P+1)/4 using the curve's precomputed big-endian representation of +// the Q. This method uses a modified version of square-and-multiply +// exponentiation over secp256k1 fieldVals to operate on bytes instead of bits, +// which offers better performance over both big.Int exponentiation and bit-wise +// square-and-multiply. +// +// NOTE: This method only works when P is intended to be the secp256k1 prime and +// is not constant time. The returned value is of magnitude 1, but is +// denormalized. +func (f *fieldVal) SqrtVal(x *fieldVal) *fieldVal { + // The following computation iteratively computes x^((P+1)/4) = x^Q + // using the recursive, piece-wise definition: + // + // x^n = (x^2)^(n/2) mod P if n is even + // x^n = x(x^2)^(n-1/2) mod P if n is odd + // + // Given n in its big-endian representation b_k, ..., b_0, x^n can be + // computed by defining the sequence r_k+1, ..., r_0, where: + // + // r_k+1 = 1 + // r_i = (r_i+1)^2 * x^b_i for i = k, ..., 0 + // + // The final value r_0 = x^n. + // + // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more + // details. + // + // This can be further optimized, by observing that the value of Q in + // secp256k1 has the value: + // + // Q = 3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c + // + // We can unroll the typical bit-wise interpretation of the + // exponentiation algorithm above to instead operate on bytes. + // This reduces the number of comparisons by an order of magnitude, + // reducing the overhead of failed branch predictions and additional + // comparisons in this method. + // + // Since there there are only 4 unique bytes of Q, this keeps the jump + // table small without the need to handle all possible 8-bit values. + // Further, we observe that 29 of the 32 bytes are 0xff; making the + // first case handle 0xff therefore optimizes the hot path. + f.SetInt(1) + for _, b := range fieldQBytes { + switch b { + + // Most common case, where all 8 bits are set. + case 0xff: + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + + // First byte of Q (0x3f), where all but the top two bits are + // set. Note that this case only applies six operations, since + // the highest bit of Q resides in bit six of the first byte. We + // ignore the first two bits, since squaring for these bits will + // result in an invalid result. We forgo squaring f before the + // first multiply, since 1^2 = 1. + case 0x3f: + f.Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + + // Byte 28 of Q (0xbf), where only bit 7 is unset. + case 0xbf: + f.Square().Mul(x) + f.Square() + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + f.Square().Mul(x) + + // Byte 31 of Q (0x0c), where only bits 3 and 4 are set. + default: + f.Square() + f.Square() + f.Square() + f.Square() + f.Square().Mul(x) + f.Square().Mul(x) + f.Square() + f.Square() + } + } + + return f +} + +// Sqrt computes the square root of f modulo the curve's prime, and stores the +// result in f. The square root is computed via exponentiation of x by the value +// Q = (P+1)/4 using the curve's precomputed big-endian representation of the Q. +// This method uses a modified version of square-and-multiply exponentiation +// over secp256k1 fieldVals to operate on bytes instead of bits, which offers +// better performance over both big.Int exponentiation and bit-wise +// square-and-multiply. +// +// NOTE: This method only works when P is intended to be the secp256k1 prime and +// is not constant time. The returned value is of magnitude 1, but is +// denormalized. +func (f *fieldVal) Sqrt() *fieldVal { + return f.SqrtVal(f) +} diff --git a/btcec/field_test.go b/btcec/field_test.go index dcfb704971..27b9730f65 100644 --- a/btcec/field_test.go +++ b/btcec/field_test.go @@ -6,6 +6,8 @@ package btcec import ( + "crypto/rand" + "fmt" "reflect" "testing" ) @@ -820,3 +822,146 @@ func TestInverse(t *testing.T) { } } } + +// randFieldVal returns a random, normalized element in the field. +func randFieldVal(t *testing.T) fieldVal { + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + t.Fatalf("unable to create random element: %v", err) + } + + var x fieldVal + return *x.SetBytes(&b).Normalize() +} + +type sqrtTest struct { + name string + in string + expected string +} + +// TestSqrt asserts that a fieldVal properly computes the square root modulo the +// sep256k1 prime. +func TestSqrt(t *testing.T) { + var tests []sqrtTest + + // No valid root exists for the negative of a square. + for i := uint(9); i > 0; i-- { + var ( + x fieldVal + s fieldVal // x^2 mod p + n fieldVal // -x^2 mod p + ) + + x.SetInt(i) + s.SquareVal(&x).Normalize() + n.NegateVal(&s, 1).Normalize() + + tests = append(tests, sqrtTest{ + name: fmt.Sprintf("-%d", i), + in: fmt.Sprintf("%x", *n.Bytes()), + }) + } + + // A root should exist for true squares. + for i := uint(0); i < 10; i++ { + var ( + x fieldVal + s fieldVal // x^2 mod p + ) + + x.SetInt(i) + s.SquareVal(&x).Normalize() + + tests = append(tests, sqrtTest{ + name: fmt.Sprintf("%d", i), + in: fmt.Sprintf("%x", *s.Bytes()), + expected: fmt.Sprintf("%x", *x.Bytes()), + }) + } + + // Compute a non-square element, by negating if it has a root. + ns := randFieldVal(t) + if new(fieldVal).SqrtVal(&ns).Square().Equals(&ns) { + ns.Negate(1).Normalize() + } + + // For large random field values, test that: + // 1) its square has a valid root. + // 2) the negative of its square has no root. + // 3) the product of its square with a non-square has no root. + for i := 0; i < 10; i++ { + var ( + x fieldVal + s fieldVal // x^2 mod p + n fieldVal // -x^2 mod p + m fieldVal // ns*x^2 mod p + ) + + x = randFieldVal(t) + s.SquareVal(&x).Normalize() + n.NegateVal(&s, 1).Normalize() + m.Mul2(&s, &ns).Normalize() + + // A root should exist for true squares. + tests = append(tests, sqrtTest{ + name: fmt.Sprintf("%x", *s.Bytes()), + in: fmt.Sprintf("%x", *s.Bytes()), + expected: fmt.Sprintf("%x", *x.Bytes()), + }) + + // No valid root exists for the negative of a square. + tests = append(tests, sqrtTest{ + name: fmt.Sprintf("-%x", *s.Bytes()), + in: fmt.Sprintf("%x", *n.Bytes()), + }) + + // No root should be computed for product of a square and + // non-square. + tests = append(tests, sqrtTest{ + name: fmt.Sprintf("ns*%x", *s.Bytes()), + in: fmt.Sprintf("%x", *m.Bytes()), + }) + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testSqrt(t, test) + }) + } +} + +func testSqrt(t *testing.T, test sqrtTest) { + var ( + f fieldVal + root fieldVal + rootNeg fieldVal + ) + + f.SetHex(test.in).Normalize() + + // Compute sqrt(f) and its negative. + root.SqrtVal(&f).Normalize() + rootNeg.NegateVal(&root, 1).Normalize() + + switch { + + // If we expect a square root, verify that either the computed square + // root is +/- the expected value. + case len(test.expected) > 0: + var expected fieldVal + expected.SetHex(test.expected).Normalize() + if !root.Equals(&expected) && !rootNeg.Equals(&expected) { + t.Fatalf("fieldVal.Sqrt incorrect root\n"+ + "got: %v\ngot_neg: %v\nwant: %v", + root, rootNeg, expected) + } + + // Otherwise, we expect this input not to have a square root. + default: + if root.Square().Equals(&f) || rootNeg.Square().Equals(&f) { + t.Fatalf("fieldVal.Sqrt root should not exist\n"+ + "got: %v\ngot_neg: %v", root, rootNeg) + } + } +} diff --git a/btcec/pubkey.go b/btcec/pubkey.go index cf49807522..3c9d5d02d2 100644 --- a/btcec/pubkey.go +++ b/btcec/pubkey.go @@ -22,41 +22,40 @@ func isOdd(a *big.Int) bool { return a.Bit(0) == 1 } -// decompressPoint decompresses a point on the given curve given the X point and +// decompressPoint decompresses a point on the secp256k1 curve given the X point and // the solution to use. -func decompressPoint(curve *KoblitzCurve, x *big.Int, ybit bool) (*big.Int, error) { - // TODO: This will probably only work for secp256k1 due to - // optimizations. +func decompressPoint(curve *KoblitzCurve, bigX *big.Int, ybit bool) (*big.Int, error) { + var x fieldVal + x.SetByteSlice(bigX.Bytes()) - // Y = +-sqrt(x^3 + B) - x3 := new(big.Int).Mul(x, x) - x3.Mul(x3, x) - x3.Add(x3, curve.Params().B) - x3.Mod(x3, curve.Params().P) + // Compute x^3 + B mod p. + var x3 fieldVal + x3.SquareVal(&x).Mul(&x) + x3.Add(curve.fieldB).Normalize() // Now calculate sqrt mod p of x^3 + B // This code used to do a full sqrt based on tonelli/shanks, // but this was replaced by the algorithms referenced in // https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294 - y := new(big.Int).Exp(x3, curve.QPlus1Div4(), curve.Params().P) - - if ybit != isOdd(y) { - y.Sub(curve.Params().P, y) + var y fieldVal + y.SqrtVal(&x3).Normalize() + if ybit != y.IsOdd() { + y.Negate(1).Normalize() } // Check that y is a square root of x^3 + B. - y2 := new(big.Int).Mul(y, y) - y2.Mod(y2, curve.Params().P) - if y2.Cmp(x3) != 0 { + var y2 fieldVal + y2.SquareVal(&y).Normalize() + if !y2.Equals(&x3) { return nil, fmt.Errorf("invalid square root") } // Verify that y-coord has expected parity. - if ybit != isOdd(y) { + if ybit != y.IsOdd() { return nil, fmt.Errorf("ybit doesn't match oddness") } - return y, nil + return new(big.Int).SetBytes(y.Bytes()[:]), nil } const ( @@ -102,6 +101,17 @@ func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err err if format == pubkeyHybrid && ybit != isOdd(pubkey.Y) { return nil, fmt.Errorf("ybit doesn't match oddness") } + + if pubkey.X.Cmp(pubkey.Curve.Params().P) >= 0 { + return nil, fmt.Errorf("pubkey X parameter is >= to P") + } + if pubkey.Y.Cmp(pubkey.Curve.Params().P) >= 0 { + return nil, fmt.Errorf("pubkey Y parameter is >= to P") + } + if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { + return nil, fmt.Errorf("pubkey isn't on secp256k1 curve") + } + case PubKeyBytesLenCompressed: // format is 0x2 | solution, // solution determines which solution of the curve we use. @@ -115,20 +125,12 @@ func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err err if err != nil { return nil, err } + default: // wrong! return nil, fmt.Errorf("invalid pub key length %d", len(pubKeyStr)) } - if pubkey.X.Cmp(pubkey.Curve.Params().P) >= 0 { - return nil, fmt.Errorf("pubkey X parameter is >= to P") - } - if pubkey.Y.Cmp(pubkey.Curve.Params().P) >= 0 { - return nil, fmt.Errorf("pubkey Y parameter is >= to P") - } - if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { - return nil, fmt.Errorf("pubkey isn't on secp256k1 curve") - } return &pubkey, nil } diff --git a/btcec/signature.go b/btcec/signature.go index f1c4377499..deedd172d8 100644 --- a/btcec/signature.go +++ b/btcec/signature.go @@ -276,7 +276,7 @@ func hashToInt(hash []byte, c elliptic.Curve) *big.Int { } // recoverKeyFromSignature recovers a public key from the signature "sig" on the -// given message hash "msg". Based on the algorithm found in section 5.1.5 of +// given message hash "msg". Based on the algorithm found in section 4.1.6 of // SEC 1 Ver 2.0, page 47-48 (53 and 54 in the pdf). This performs the details // in the inner loop in Step 1. The counter provided is actually the j parameter // of the loop * 2 - on the first iteration of j we do the R case, else the -R diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 4210ea0b61..16319ab4f8 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -638,6 +638,7 @@ func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinE type SendRawTransactionCmd struct { HexTx string AllowHighFees *bool `jsonrpcdefault:"false"` + MaxFeeRate *int32 } // NewSendRawTransactionCmd returns a new instance which can be used to issue a @@ -652,6 +653,17 @@ func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransac } } +// NewSendRawTransactionCmd returns a new instance which can be used to issue a +// sendrawtransaction JSON-RPC command to a bitcoind node. +// +// A 0 maxFeeRate indicates that a maximum fee rate won't be enforced. +func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTransactionCmd { + return &SendRawTransactionCmd{ + HexTx: hexTx, + MaxFeeRate: &maxFeeRate, + } +} + // SetGenerateCmd defines the setgenerate JSON-RPC command. type SetGenerateCmd struct { Generate bool diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 30c6369a32..7e6c710766 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -90,28 +90,61 @@ type SoftForkDescription struct { // Bip9SoftForkDescription describes the current state of a defined BIP0009 // version bits soft-fork. type Bip9SoftForkDescription struct { - Status string `json:"status"` - Bit uint8 `json:"bit"` - StartTime int64 `json:"startTime"` - Timeout int64 `json:"timeout"` - Since int32 `json:"since"` + Status string `json:"status"` + Bit uint8 `json:"bit"` + StartTime1 int64 `json:"startTime"` + StartTime2 int64 `json:"start_time"` + Timeout int64 `json:"timeout"` + Since int32 `json:"since"` +} + +// StartTime returns the starting time of the softfork as a Unix epoch. +func (d *Bip9SoftForkDescription) StartTime() int64 { + if d.StartTime1 != 0 { + return d.StartTime1 + } + return d.StartTime2 +} + +// SoftForks describes the current softforks enabled by the backend. Softforks +// activated through BIP9 are grouped together separate from any other softforks +// with different activation types. +type SoftForks struct { + SoftForks []*SoftForkDescription `json:"softforks"` + Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"` +} + +// UnifiedSoftForks describes a softforks in a general manner, irrespective of +// its activation type. This was a format introduced by bitcoind v0.19.0 +type UnifiedSoftFork struct { + Type string `json:"type"` + BIP9SoftForkDescription *Bip9SoftForkDescription `json:"bip9"` + Height int32 `json:"height"` + Active bool `json:"active"` +} + +// UnifiedSoftForks describes the current softforks enabled the by the backend +// in a unified manner, i.e, softforks with different activation types are +// grouped together. This was a format introduced by bitcoind v0.19.0 +type UnifiedSoftForks struct { + SoftForks map[string]*UnifiedSoftFork `json:"softforks"` } // GetBlockChainInfoResult models the data returned from the getblockchaininfo // command. type GetBlockChainInfoResult struct { - Chain string `json:"chain"` - Blocks int32 `json:"blocks"` - Headers int32 `json:"headers"` - BestBlockHash string `json:"bestblockhash"` - Difficulty float64 `json:"difficulty"` - MedianTime int64 `json:"mediantime"` - VerificationProgress float64 `json:"verificationprogress,omitempty"` - Pruned bool `json:"pruned"` - PruneHeight int32 `json:"pruneheight,omitempty"` - ChainWork string `json:"chainwork,omitempty"` - SoftForks []*SoftForkDescription `json:"softforks"` - Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"` + Chain string `json:"chain"` + Blocks int32 `json:"blocks"` + Headers int32 `json:"headers"` + BestBlockHash string `json:"bestblockhash"` + Difficulty float64 `json:"difficulty"` + MedianTime int64 `json:"mediantime"` + VerificationProgress float64 `json:"verificationprogress,omitempty"` + Pruned bool `json:"pruned"` + PruneHeight int32 `json:"pruneheight,omitempty"` + ChainWork string `json:"chainwork,omitempty"` + *SoftForks + *UnifiedSoftForks } // GetBlockTemplateResultTx models the transactions field of the @@ -265,6 +298,7 @@ type GetPeerInfoResult struct { type GetRawMempoolVerboseResult struct { Size int32 `json:"size"` Vsize int32 `json:"vsize"` + Weight int32 `json:"weight"` Fee float64 `json:"fee"` Time int64 `json:"time"` Height int64 `json:"height"` @@ -505,6 +539,7 @@ type TxRawResult struct { Hash string `json:"hash,omitempty"` Size int32 `json:"size,omitempty"` Vsize int32 `json:"vsize,omitempty"` + Weight int32 `json:"weight,omitempty"` Version int32 `json:"version"` LockTime uint32 `json:"locktime"` Vin []Vin `json:"vin"` @@ -523,6 +558,7 @@ type SearchRawTransactionsResult struct { Hash string `json:"hash"` Size string `json:"size"` Vsize string `json:"vsize"` + Weight string `json:"weight"` Version int32 `json:"version"` LockTime uint32 `json:"locktime"` Vin []VinPrevOut `json:"vin"` diff --git a/cmd/ltcctl/version.go b/cmd/ltcctl/version.go index fb147bcda5..f65cacef7e 100644 --- a/cmd/ltcctl/version.go +++ b/cmd/ltcctl/version.go @@ -17,8 +17,8 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr // versioning 2.0.0 spec (http://semver.org/). const ( appMajor uint = 0 - appMinor uint = 12 - appPatch uint = 0 + appMinor uint = 20 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. diff --git a/docs/README.md b/docs/README.md index fc7818bbcf..eb1470c43e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,7 +49,7 @@ transactions based on miner requirements ("standard" transactions). One key difference between ltcd and Bitcoin Core is that ltcd does *NOT* include wallet functionality and this was a very intentional design decision. See the -blog entry [here](https://blog.conformal.com/btcd-not-your-moms-bitcoin-daemon) +blog entry [here](https://web.archive.org/web/20171125143919/https://blog.conformal.com/btcd-not-your-moms-bitcoin-daemon) for more details. This means you can't actually make or receive payments directly with ltcd. That functionality is provided by the [btcwallet](https://github.com/btcsuite/btcwallet) and diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index daccab3cc5..d05ddda615 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -484,7 +484,7 @@ Example Return|`{`
  `"bytes": 310768,`
  `"size": |Description|Returns an array of hashes for all of the transactions currently in the memory pool.
The `verbose` flag specifies that each transaction is returned as a JSON object.| |Notes|Since ltcd does not perform any mining, the priority related fields `startingpriority` and `currentpriority` that are available when the `verbose` flag is set are always 0.| |Returns (verbose=false)|`[ (json array of string)`
  `"transactionhash", (string) hash of the transaction`
  `...`
`]`| -|Returns (verbose=true)|`{ (json object)`
  `"transactionhash": { (json object)`
    `"size": n, (numeric) transaction size in bytes`
    `"vsize": n, (numeric) transaction virtual size`
    `"fee" : n, (numeric) transaction fee in bitcoins`
    `"time": n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT`
    `"height": n, (numeric) block height when transaction entered the pool`
    `"startingpriority": n, (numeric) priority when transaction entered the pool`
    `"currentpriority": n, (numeric) current priority`
    `"depends": [ (json array) unconfirmed transactions used as inputs for this transaction`
      `"transactionhash", (string) hash of the parent transaction`
      `...`
    `]`
  `}, ...`
`}`| +|Returns (verbose=true)|`{ (json object)`
  `"transactionhash": { (json object)`
    `"size": n, (numeric) transaction size in bytes`
    `"vsize": n, (numeric) transaction virtual size`
    `"weight": n, (numeric) The transaction's weight (between vsize*4-3 and vsize*4)`
    `"fee" : n, (numeric) transaction fee in bitcoins`
    `"time": n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT`
    `"height": n, (numeric) block height when transaction entered the pool`
    `"startingpriority": n, (numeric) priority when transaction entered the pool`
    `"currentpriority": n, (numeric) current priority`
    `"depends": [ (json array) unconfirmed transactions used as inputs for this transaction`
      `"transactionhash", (string) hash of the parent transaction`
      `...`
    `]`
  `}, ...`
`}`| |Example Return (verbose=false)|`[`
  `"3480058a397b6ffcc60f7e3345a61370fded1ca6bef4b58156ed17987f20d4e7",`
  `"cbfe7c056a358c3a1dbced5a22b06d74b8650055d5195c1c2469e6b63a41514a"`
`]`| |Example Return (verbose=true)|`{`
  `"1697a19cede08694278f19584e8dcc87945f40c6b59a942dd8906f133ad3f9cc": {`
    `"size": 226,`
    `"fee" : 0.0001,`
    `"time": 1387992789,`
    `"height": 276836,`
    `"startingpriority": 0,`
    `"currentpriority": 0,`
    `"depends": [`
      `"aa96f672fcc5a1ec6a08a94aa46d6b789799c87bd6542967da25a96b2dee0afb",`
    `]`
`}`| [Return to Overview](#MethodOverview)
diff --git a/go.mod b/go.mod index ccaf91a934..1059bbe50b 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.12 require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcutil v1.0.1 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd + github.com/btcsuite/snappy-go v1.0.0 // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 github.com/btcsuite/winsvc v1.0.0 github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 @@ -13,6 +15,5 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/kkdai/bstream v1.0.0 // indirect github.com/ltcsuite/ltcutil v0.0.0-20191227053721-6bec450ea6ad - golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 + golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d ) - diff --git a/go.sum b/go.sum index aa9f920875..ceedca22bb 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,17 @@ github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.1 h1:GKOz8BnRjYrb/JTKgaOk+zh26NWNdSNvdvv0xoAZMSA= +github.com/btcsuite/btcutil v1.0.1/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= @@ -24,19 +30,27 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QU github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= github.com/ltcsuite/ltcd v0.0.0-20191226025254-c36909602b91/go.mod h1:L6iIoZ4OPgo4Wq/3M20kKViHXYlpiEw572l6jeXvoi8= +github.com/ltcsuite/ltcutil v0.0.0-20191227053721-6bec450ea6ad h1:ZSvrs99WjvbTwJE1NzCi6OhpOHFRamjTf7lPgo1p20w= +github.com/ltcsuite/ltcutil v0.0.0-20191227053721-6bec450ea6ad/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= @@ -58,5 +72,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/integration/bip0009_test.go b/integration/bip0009_test.go index 30b9283f35..82ce97080a 100644 --- a/integration/bip0009_test.go +++ b/integration/bip0009_test.go @@ -102,7 +102,7 @@ func assertSoftForkStatus(r *rpctest.Harness, t *testing.T, forkKey string, stat } // Ensure the key is available. - desc, ok := info.Bip9SoftForks[forkKey] + desc, ok := info.SoftForks.Bip9SoftForks[forkKey] if !ok { _, _, line, _ := runtime.Caller(1) t.Fatalf("assertion failed at line %d: softfork status for %q "+ diff --git a/mempool/mempool.go b/mempool/mempool.go index 46889674c3..f2a7fb0819 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -1509,6 +1509,7 @@ func (mp *TxPool) RawMempoolVerbose() map[string]*btcjson.GetRawMempoolVerboseRe mpd := &btcjson.GetRawMempoolVerboseResult{ Size: int32(tx.MsgTx().SerializeSize()), Vsize: int32(GetTxVirtualSize(tx)), + Weight: int32(blockchain.GetTransactionWeight(tx)), Fee: ltcutil.Amount(desc.Fee).ToBTC(), Time: desc.Added.Unix(), Height: int64(desc.Height), diff --git a/peer/peer.go b/peer/peer.go index d29c4c1d71..555aefffd4 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1373,20 +1373,12 @@ out: break out case *wire.MsgVerAck: - - // No read lock is necessary because verAckReceived is not written - // to in any other goroutine. - if p.verAckReceived { - log.Infof("Already received 'verack' from peer %v -- "+ - "disconnecting", p) - break out - } - p.flagsMtx.Lock() - p.verAckReceived = true - p.flagsMtx.Unlock() - if p.cfg.Listeners.OnVerAck != nil { - p.cfg.Listeners.OnVerAck(p, msg) - } + // Limit to one verack message per peer. + p.PushRejectMsg( + msg.Command(), wire.RejectDuplicate, + "duplicate verack message", nil, true, + ) + break out case *wire.MsgGetAddr: if p.cfg.Listeners.OnGetAddr != nil { @@ -1970,6 +1962,40 @@ func (p *Peer) readRemoteVersionMsg() error { return nil } +// readRemoteVerAckMsg waits for the next message to arrive from the remote +// peer. If this message is not a verack message, then an error is returned. +// This method is to be used as part of the version negotiation upon a new +// connection. +func (p *Peer) readRemoteVerAckMsg() error { + // Read the next message from the wire. + remoteMsg, _, err := p.readMessage(wire.LatestEncoding) + if err != nil { + return err + } + + // It should be a verack message, otherwise send a reject message to the + // peer explaining why. + msg, ok := remoteMsg.(*wire.MsgVerAck) + if !ok { + reason := "a verack message must follow version" + rejectMsg := wire.NewMsgReject( + msg.Command(), wire.RejectMalformed, reason, + ) + _ = p.writeMessage(rejectMsg, wire.LatestEncoding) + return errors.New(reason) + } + + p.flagsMtx.Lock() + p.verAckReceived = true + p.flagsMtx.Unlock() + + if p.cfg.Listeners.OnVerAck != nil { + p.cfg.Listeners.OnVerAck(p, msg) + } + + return nil +} + // localVersionMsg creates a version message that can be used to send to the // remote peer. func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { @@ -2042,26 +2068,53 @@ func (p *Peer) writeLocalVersionMsg() error { return p.writeMessage(localVerMsg, wire.LatestEncoding) } -// negotiateInboundProtocol waits to receive a version message from the peer -// then sends our version message. If the events do not occur in that order then -// it returns an error. +// negotiateInboundProtocol performs the negotiation protocol for an inbound +// peer. The events should occur in the following order, otherwise an error is +// returned: +// +// 1. Remote peer sends their version. +// 2. We send our version. +// 3. We send our verack. +// 4. Remote peer sends their verack. func (p *Peer) negotiateInboundProtocol() error { if err := p.readRemoteVersionMsg(); err != nil { return err } - return p.writeLocalVersionMsg() + if err := p.writeLocalVersionMsg(); err != nil { + return err + } + + err := p.writeMessage(wire.NewMsgVerAck(), wire.LatestEncoding) + if err != nil { + return err + } + + return p.readRemoteVerAckMsg() } -// negotiateOutboundProtocol sends our version message then waits to receive a -// version message from the peer. If the events do not occur in that order then -// it returns an error. +// negotiateOutoundProtocol performs the negotiation protocol for an outbound +// peer. The events should occur in the following order, otherwise an error is +// returned: +// +// 1. We send our version. +// 2. Remote peer sends their version. +// 3. Remote peer sends their verack. +// 4. We send our verack. func (p *Peer) negotiateOutboundProtocol() error { if err := p.writeLocalVersionMsg(); err != nil { return err } - return p.readRemoteVersionMsg() + if err := p.readRemoteVersionMsg(); err != nil { + return err + } + + if err := p.readRemoteVerAckMsg(); err != nil { + return err + } + + return p.writeMessage(wire.NewMsgVerAck(), wire.LatestEncoding) } // start begins processing input and output messages. @@ -2098,8 +2151,6 @@ func (p *Peer) start() error { go p.outHandler() go p.pingHandler() - // Send our verack message now that the IO processing machinery has started. - p.QueueMessage(wire.NewMsgVerAck(), nil) return nil } diff --git a/release/GIT-GPG-KEY-conformal.txt b/release/GIT-GPG-KEY-conformal.txt deleted file mode 100644 index a6d1a2562d..0000000000 --- a/release/GIT-GPG-KEY-conformal.txt +++ /dev/null @@ -1,74 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -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 -=iic8 ------END PGP PUBLIC KEY BLOCK----- diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000000..10d1c80cc0 --- /dev/null +++ b/release/README.md @@ -0,0 +1,71 @@ +# `btcd`'s Reproducible Build System + +This package contains the build script that the `btcd` project uses in order to +build binaries for each new release. As of `go1.13`, with some new build flags, +binaries are now reproducible, allowing developers to build the binary on +distinct machines, and end up with a byte-for-byte identical binary. However, +this wasn't _fully_ solved in `go1.13`, as the build system still includes the +directory the binary is built into the binary itself. As a result, our scripts +utilize a work around needed until `go1.13.2`. + +## Building a New Release + +### macOS/Linux/Windows (WSL) + +No prior set up is needed on Linux or macOS is required in order to build the +release binaries. However, on Windows, the only way to build the release +binaries at the moment is by using the Windows Subsystem Linux. One can build +the release binaries following these steps: + +1. `git clone https://github.com/btcsuite/btcd.git +2. `cd btcd` +3. `./build/release/release.sh # is the name of the next + release/tag` + +This will then create a directory of the form `btcd-` containing archives +of the release binaries for each supported operating system and architecture, +and a manifest file containing the hash of each archive. + +## Verifying a Release + +With `go1.13`, it's now possible for third parties to verify release binaries. +Before this version of `go`, one had to trust the release manager(s) to build the +proper binary. With this new system, third parties can now _independently_ run +the release process, and verify that all the hashes of the release binaries +match exactly that of the release binaries produced by said third parties. + +To verify a release, one must obtain the following tools (many of these come +installed by default in most Unix systems): `gpg`/`gpg2`, `shashum`, and +`tar`/`unzip`. + +Once done, verifiers can proceed with the following steps: + +1. Acquire the archive containing the release binaries for one's specific + operating system and architecture, and the manifest file along with its + signature. +2. Verify the signature of the manifest file with `gpg --verify + manifest-.txt.sig`. This will require obtaining the PGP keys which + signed the manifest file, which are included in the release notes. +3. Recompute the `SHA256` hash of the archive with `shasum -a 256 `, + locate the corresponding one in the manifest file, and ensure they match + __exactly__. + +At this point, verifiers can use the release binaries acquired if they trust +the integrity of the release manager(s). Otherwise, one can proceed with the +guide to verify the release binaries were built properly by obtaining `shasum` +and `go` (matching the same version used in the release): + +4. Extract the release binaries contained within the archive, compute their + hashes as done above, and note them down. +5. Ensure `go` is installed, matching the same version as noted in the release + notes. +6. Obtain a copy of `btcd`'s source code with `git clone + https://github.com/btcsuite/btcd` and checkout the source code of the + release with `git checkout `. +7. Proceed to verify the tag with `git verify-tag ` and compile the + binaries from source for the intended operating system and architecture with + `BTCDBUILDSYS=OS-ARCH ./build/release/release.sh `. +8. Extract the archive found in the `btcd-` directory created by the + release script and recompute the `SHA256` hash of the release binaries (btcd + and btcctl) with `shasum -a 256 `. These should match __exactly__ + as the ones noted above. diff --git a/release/release.sh b/release/release.sh new file mode 100755 index 0000000000..7b0885dac6 --- /dev/null +++ b/release/release.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Simple bash script to build basic btcd tools for all the platforms we support +# with the golang cross-compiler. +# +# Copyright (c) 2016 Company 0, LLC. +# Use of this source code is governed by the ISC +# license. + +set -e + +# If no tag specified, use date + version otherwise use tag. +if [[ $1x = x ]]; then + DATE=`date +%Y%m%d` + VERSION="01" + TAG=$DATE-$VERSION +else + TAG=$1 +fi + +go mod vendor +tar -cvzf vendor.tar.gz vendor + +PACKAGE=btcd +MAINDIR=$PACKAGE-$TAG +mkdir -p $MAINDIR + +cp vendor.tar.gz $MAINDIR/ +rm vendor.tar.gz +rm -r vendor + +PACKAGESRC="$MAINDIR/$PACKAGE-source-$TAG.tar" +git archive -o $PACKAGESRC HEAD +gzip -f $PACKAGESRC > "$PACKAGESRC.gz" + +cd $MAINDIR + +# If BTCDBUILDSYS is set the default list is ignored. Useful to release +# for a subset of systems/architectures. +SYS=${BTCDBUILDSYS:-" + darwin-386 + darwin-amd64 + dragonfly-amd64 + freebsd-386 + freebsd-amd64 + freebsd-arm + illumos-amd64 + linux-386 + linux-amd64 + linux-armv6 + linux-armv7 + linux-arm64 + linux-ppc64 + linux-ppc64le + linux-mips + linux-mipsle + linux-mips64 + linux-mips64le + linux-s390x + netbsd-386 + netbsd-amd64 + netbsd-arm + netbsd-arm64 + openbsd-386 + openbsd-amd64 + openbsd-arm + openbsd-arm64 + solaris-amd64 + windows-386 + windows-amd64 +"} + +# Use the first element of $GOPATH in the case where GOPATH is a list +# (something that is totally allowed). +PKG="github.com/btcsuite/btcd" +COMMIT=$(git describe --abbrev=40 --dirty) + +for i in $SYS; do + OS=$(echo $i | cut -f1 -d-) + ARCH=$(echo $i | cut -f2 -d-) + ARM= + + if [[ $ARCH = "armv6" ]]; then + ARCH=arm + ARM=6 + elif [[ $ARCH = "armv7" ]]; then + ARCH=arm + ARM=7 + fi + + mkdir $PACKAGE-$i-$TAG + cd $PACKAGE-$i-$TAG + + echo "Building:" $OS $ARCH $ARM + env CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH GOARM=$ARM go build -v -trimpath -ldflags="-s -w -buildid=" github.com/btcsuite/btcd + env CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH GOARM=$ARM go build -v -trimpath -ldflags="-s -w -buildid=" github.com/btcsuite/btcd/cmd/btcctl + cd .. + + if [[ $OS = "windows" ]]; then + zip -r $PACKAGE-$i-$TAG.zip $PACKAGE-$i-$TAG + else + tar -cvzf $PACKAGE-$i-$TAG.tar.gz $PACKAGE-$i-$TAG + fi + + rm -r $PACKAGE-$i-$TAG +done + +shasum -a 256 * > manifest-$TAG.txt diff --git a/rpcclient/chain.go b/rpcclient/chain.go index f2487feaa3..f057592510 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -253,21 +253,73 @@ func (c *Client) GetDifficulty() (float64, error) { // FutureGetBlockChainInfoResult is a promise to deliver the result of a // GetBlockChainInfoAsync RPC invocation (or an applicable error). -type FutureGetBlockChainInfoResult chan *response +type FutureGetBlockChainInfoResult struct { + client *Client + Response chan *response +} + +// unmarshalPartialGetBlockChainInfoResult unmarshals the response into an +// instance of GetBlockChainInfoResult without populating the SoftForks and +// UnifiedSoftForks fields. +func unmarshalPartialGetBlockChainInfoResult(res []byte) (*btcjson.GetBlockChainInfoResult, error) { + var chainInfo btcjson.GetBlockChainInfoResult + if err := json.Unmarshal(res, &chainInfo); err != nil { + return nil, err + } + return &chainInfo, nil +} + +// unmarshalGetBlockChainInfoResultSoftForks properly unmarshals the softforks +// related fields into the GetBlockChainInfoResult instance. +func unmarshalGetBlockChainInfoResultSoftForks(chainInfo *btcjson.GetBlockChainInfoResult, + version BackendVersion, res []byte) error { + + switch version { + // Versions of bitcoind on or after v0.19.0 use the unified format. + case BitcoindPost19: + var softForks btcjson.UnifiedSoftForks + if err := json.Unmarshal(res, &softForks); err != nil { + return err + } + chainInfo.UnifiedSoftForks = &softForks + + // All other versions use the original format. + default: + var softForks btcjson.SoftForks + if err := json.Unmarshal(res, &softForks); err != nil { + return err + } + chainInfo.SoftForks = &softForks + } + + return nil +} // Receive waits for the response promised by the future and returns chain info // result provided by the server. func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResult, error) { - res, err := receiveFuture(r) + res, err := receiveFuture(r.Response) + if err != nil { + return nil, err + } + chainInfo, err := unmarshalPartialGetBlockChainInfoResult(res) if err != nil { return nil, err } - var chainInfo btcjson.GetBlockChainInfoResult - if err := json.Unmarshal(res, &chainInfo); err != nil { + // Inspect the version to determine how we'll need to parse the + // softforks from the response. + version, err := r.client.BackendVersion() + if err != nil { return nil, err } - return &chainInfo, nil + + err = unmarshalGetBlockChainInfoResultSoftForks(chainInfo, version, res) + if err != nil { + return nil, err + } + + return chainInfo, nil } // GetBlockChainInfoAsync returns an instance of a type that can be used to get @@ -277,7 +329,10 @@ func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResu // See GetBlockChainInfo for the blocking version and more details. func (c *Client) GetBlockChainInfoAsync() FutureGetBlockChainInfoResult { cmd := btcjson.NewGetBlockChainInfoCmd() - return c.sendCmd(cmd) + return FutureGetBlockChainInfoResult{ + client: c, + Response: c.sendCmd(cmd), + } } // GetBlockChainInfo returns information related to the processing state of diff --git a/rpcclient/chain_test.go b/rpcclient/chain_test.go new file mode 100644 index 0000000000..e32d547ce3 --- /dev/null +++ b/rpcclient/chain_test.go @@ -0,0 +1,92 @@ +package rpcclient + +import "testing" + +// TestUnmarshalGetBlockChainInfoResult ensures that the SoftForks and +// UnifiedSoftForks fields of GetBlockChainInfoResult are properly unmarshaled +// when using the expected backend version. +func TestUnmarshalGetBlockChainInfoResultSoftForks(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + version BackendVersion + res []byte + compatible bool + }{ + { + name: "bitcoind < 0.19.0 with separate softforks", + version: BitcoindPre19, + res: []byte(`{"softforks": [{"version": 2}]}`), + compatible: true, + }, + { + name: "bitcoind >= 0.19.0 with separate softforks", + version: BitcoindPost19, + res: []byte(`{"softforks": [{"version": 2}]}`), + compatible: false, + }, + { + name: "bitcoind < 0.19.0 with unified softforks", + version: BitcoindPre19, + res: []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`), + compatible: false, + }, + { + name: "bitcoind >= 0.19.0 with unified softforks", + version: BitcoindPost19, + res: []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`), + compatible: true, + }, + } + + for _, test := range tests { + success := t.Run(test.name, func(t *testing.T) { + // We'll start by unmarshaling the JSON into a struct. + // The SoftForks and UnifiedSoftForks field should not + // be set yet, as they are unmarshaled within a + // different function. + info, err := unmarshalPartialGetBlockChainInfoResult(test.res) + if err != nil { + t.Fatal(err) + } + if info.SoftForks != nil { + t.Fatal("expected SoftForks to be empty") + } + if info.UnifiedSoftForks != nil { + t.Fatal("expected UnifiedSoftForks to be empty") + } + + // Proceed to unmarshal the softforks of the response + // with the expected version. If the version is + // incompatible with the response, then this should + // fail. + err = unmarshalGetBlockChainInfoResultSoftForks( + info, test.version, test.res, + ) + if test.compatible && err != nil { + t.Fatalf("unable to unmarshal softforks: %v", err) + } + if !test.compatible && err == nil { + t.Fatal("expected to not unmarshal softforks") + } + if !test.compatible { + return + } + + // If the version is compatible with the response, we + // should expect to see the proper softforks field set. + if test.version == BitcoindPost19 && + info.SoftForks != nil { + t.Fatal("expected SoftForks to be empty") + } + if test.version == BitcoindPre19 && + info.UnifiedSoftForks != nil { + t.Fatal("expected UnifiedSoftForks to be empty") + } + }) + if !success { + return + } + } +} diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 823b48f5e2..c6c635b091 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -19,6 +19,7 @@ import ( "net" "net/http" "net/url" + "strings" "sync" "sync/atomic" "time" @@ -103,6 +104,22 @@ type jsonRequest struct { responseChan chan *response } +// BackendVersion represents the version of the backend the client is currently +// connected to. +type BackendVersion uint8 + +const ( + // BitcoindPre19 represents a bitcoind version before 0.19.0. + BitcoindPre19 BackendVersion = iota + + // BitcoindPost19 represents a bitcoind version equal to or greater than + // 0.19.0. + BitcoindPost19 + + // Btcd represents a catch-all btcd version. + Btcd +) + // Client represents a Bitcoin RPC client which allows easy access to the // various RPC methods available on a Bitcoin RPC server. Each of the wrapper // functions handle the details of converting the passed and return types to and @@ -129,6 +146,11 @@ type Client struct { // POST mode. httpClient *http.Client + // backendVersion is the version of the backend the client is currently + // connected to. This should be retrieved through GetVersion. + backendVersionMu sync.Mutex + backendVersion *BackendVersion + // mtx is a mutex to protect access to connection related fields. mtx sync.Mutex @@ -659,6 +681,12 @@ out: log.Infof("Reestablished connection to RPC server %s", c.config.Host) + // Reset the version in case the backend was + // disconnected due to an upgrade. + c.backendVersionMu.Lock() + c.backendVersion = nil + c.backendVersionMu.Unlock() + // Reset the connection state and signal the reconnect // has happened. c.wsConn = wsConn @@ -1332,3 +1360,84 @@ func (c *Client) Connect(tries int) error { // All connection attempts failed, so return the last error. return err } + +const ( + // bitcoind19Str is the string representation of bitcoind v0.19.0. + bitcoind19Str = "0.19.0" + + // bitcoindVersionPrefix specifies the prefix included in every bitcoind + // version exposed through GetNetworkInfo. + bitcoindVersionPrefix = "/Satoshi:" + + // bitcoindVersionSuffix specifies the suffix included in every bitcoind + // version exposed through GetNetworkInfo. + bitcoindVersionSuffix = "/" +) + +// parseBitcoindVersion parses the bitcoind version from its string +// representation. +func parseBitcoindVersion(version string) BackendVersion { + // Trim the version of its prefix and suffix to determine the + // appropriate version number. + version = strings.TrimPrefix( + strings.TrimSuffix(version, bitcoindVersionSuffix), + bitcoindVersionPrefix, + ) + switch { + case version < bitcoind19Str: + return BitcoindPre19 + default: + return BitcoindPost19 + } +} + +// BackendVersion retrieves the version of the backend the client is currently +// connected to. +func (c *Client) BackendVersion() (BackendVersion, error) { + c.backendVersionMu.Lock() + defer c.backendVersionMu.Unlock() + + if c.backendVersion != nil { + return *c.backendVersion, nil + } + + // We'll start by calling GetInfo. This method doesn't exist for + // bitcoind nodes as of v0.16.0, so we'll assume the client is connected + // to a btcd backend if it does exist. + info, err := c.GetInfo() + + switch err := err.(type) { + // Parse the btcd version and cache it. + case nil: + log.Debugf("Detected btcd version: %v", info.Version) + version := Btcd + c.backendVersion = &version + return *c.backendVersion, nil + + // Inspect the RPC error to ensure the method was not found, otherwise + // we actually ran into an error. + case *btcjson.RPCError: + if err.Code != btcjson.ErrRPCMethodNotFound.Code { + return 0, fmt.Errorf("unable to detect btcd version: "+ + "%v", err) + } + + default: + return 0, fmt.Errorf("unable to detect btcd version: %v", err) + } + + // Since the GetInfo method was not found, we assume the client is + // connected to a bitcoind backend, which exposes its version through + // GetNetworkInfo. + networkInfo, err := c.GetNetworkInfo() + if err != nil { + return 0, fmt.Errorf("unable to detect bitcoind version: %v", err) + } + + // Parse the bitcoind version and cache it. + log.Debugf("Detected bitcoind version: %v", networkInfo.SubVersion) + version := parseBitcoindVersion(networkInfo.SubVersion) + c.backendVersion = &version + + return *c.backendVersion, nil +} diff --git a/rpcclient/net.go b/rpcclient/net.go index c93ff0f81b..45874c0bca 100644 --- a/rpcclient/net.go +++ b/rpcclient/net.go @@ -244,6 +244,43 @@ func (c *Client) Ping() error { return c.PingAsync().Receive() } +// FutureGetNetworkInfoResult is a future promise to deliver the result of a +// GetNetworkInfoAsync RPC invocation (or an applicable error). +type FutureGetNetworkInfoResult chan *response + +// Receive waits for the response promised by the future and returns data about +// the current network. +func (r FutureGetNetworkInfoResult) Receive() (*btcjson.GetNetworkInfoResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as an array of getpeerinfo result objects. + var networkInfo btcjson.GetNetworkInfoResult + err = json.Unmarshal(res, &networkInfo) + if err != nil { + return nil, err + } + + return &networkInfo, nil +} + +// GetNetworkInfoAsync returns an instance of a type that can be used to get the +// result of the RPC at some future time by invoking the Receive function on the +// returned instance. +// +// See GetNetworkInfo for the blocking version and more details. +func (c *Client) GetNetworkInfoAsync() FutureGetNetworkInfoResult { + cmd := btcjson.NewGetNetworkInfoCmd() + return c.sendCmd(cmd) +} + +// GetNetworkInfo returns data about the current network. +func (c *Client) GetNetworkInfo() (*btcjson.GetNetworkInfoResult, error) { + return c.GetNetworkInfoAsync().Receive() +} + // FutureGetPeerInfoResult is a future promise to deliver the result of a // GetPeerInfoAsync RPC invocation (or an applicable error). type FutureGetPeerInfoResult chan *response diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 9960f16f86..7d1f13f501 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -15,6 +15,12 @@ import ( "github.com/ltcsuite/ltcutil" ) +const ( + // defaultMaxFeeRate is the default maximum fee rate in sat/KB enforced + // by bitcoind v0.19.0 or after for transaction broadcast. + defaultMaxFeeRate = ltcutil.SatoshiPerBitcoin / 10 +) + // SigHashType enumerates the available signature hashing types that the // SignRawTransaction function accepts. type SigHashType string @@ -296,7 +302,31 @@ func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) Fut txHex = hex.EncodeToString(buf.Bytes()) } - cmd := btcjson.NewSendRawTransactionCmd(txHex, &allowHighFees) + // Due to differences in the sendrawtransaction API for different + // backends, we'll need to inspect our version and construct the + // appropriate request. + version, err := c.BackendVersion() + if err != nil { + return newFutureError(err) + } + + var cmd *btcjson.SendRawTransactionCmd + switch version { + // Starting from bitcoind v0.19.0, the MaxFeeRate field should be used. + case BitcoindPost19: + // Using a 0 MaxFeeRate is interpreted as a maximum fee rate not + // being enforced by bitcoind. + var maxFeeRate int32 + if !allowHighFees { + maxFeeRate = defaultMaxFeeRate + } + cmd = btcjson.NewBitcoindSendRawTransactionCmd(txHex, maxFeeRate) + + // Otherwise, use the AllowHighFees field. + default: + cmd = btcjson.NewSendRawTransactionCmd(txHex, &allowHighFees) + } + return c.sendCmd(cmd) } diff --git a/rpcserver.go b/rpcserver.go index 44eb4c9e2c..50e3930906 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -756,6 +756,7 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, Hash: mtx.WitnessHash().String(), Size: int32(mtx.SerializeSize()), Vsize: int32(mempool.GetTxVirtualSize(ltcutil.NewTx(mtx))), + Weight: int32(blockchain.GetTransactionWeight(ltcutil.NewTx(mtx))), Vin: createVinList(mtx), Vout: createVoutList(mtx, chainParams, nil), Version: mtx.Version, @@ -1197,14 +1198,16 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str Difficulty: getDifficultyRatio(chainSnapshot.Bits, params), MedianTime: chainSnapshot.MedianTime.Unix(), Pruned: false, - Bip9SoftForks: make(map[string]*btcjson.Bip9SoftForkDescription), + SoftForks: &btcjson.SoftForks{ + Bip9SoftForks: make(map[string]*btcjson.Bip9SoftForkDescription), + }, } // Next, populate the response with information describing the current // status of soft-forks deployed via the super-majority block // signalling mechanism. height := chainSnapshot.Height - chainInfo.SoftForks = []*btcjson.SoftForkDescription{ + chainInfo.SoftForks.SoftForks = []*btcjson.SoftForkDescription{ { ID: "bip34", Version: 2, @@ -1280,11 +1283,11 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str // Finally, populate the soft-fork description with all the // information gathered above. - chainInfo.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ - Status: strings.ToLower(statusString), - Bit: deploymentDetails.BitNumber, - StartTime: int64(deploymentDetails.StartTime), - Timeout: int64(deploymentDetails.ExpireTime), + chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ + Status: strings.ToLower(statusString), + Bit: deploymentDetails.BitNumber, + StartTime2: int64(deploymentDetails.StartTime), + Timeout: int64(deploymentDetails.ExpireTime), } } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 6914af3df5..c84d176f93 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -172,21 +172,18 @@ var helpDescsEnUS = map[string]string{ "getblockchaininfo--synopsis": "Returns information about the current blockchain state and the status of any active soft-fork deployments.", // GetBlockChainInfoResult help. - "getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", - "getblockchaininforesult-blocks": "The number of blocks in the best known chain", - "getblockchaininforesult-headers": "The number of headers that we've gathered for in the best known chain", - "getblockchaininforesult-bestblockhash": "The block hash for the latest block in the main chain", - "getblockchaininforesult-difficulty": "The current chain difficulty", - "getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", - "getblockchaininforesult-verificationprogress": "An estimate for how much of the best chain we've verified", - "getblockchaininforesult-pruned": "A bool that indicates if the node is pruned or not", - "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", - "getblockchaininforesult-chainwork": "The total cumulative work in the best chain", - "getblockchaininforesult-softforks": "The status of the super-majority soft-forks", - "getblockchaininforesult-bip9_softforks": "JSON object describing active BIP0009 deployments", - "getblockchaininforesult-bip9_softforks--key": "bip9_softforks", - "getblockchaininforesult-bip9_softforks--value": "An object describing a particular BIP009 deployment", - "getblockchaininforesult-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", + "getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", + "getblockchaininforesult-blocks": "The number of blocks in the best known chain", + "getblockchaininforesult-headers": "The number of headers that we've gathered for in the best known chain", + "getblockchaininforesult-bestblockhash": "The block hash for the latest block in the main chain", + "getblockchaininforesult-difficulty": "The current chain difficulty", + "getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", + "getblockchaininforesult-verificationprogress": "An estimate for how much of the best chain we've verified", + "getblockchaininforesult-pruned": "A bool that indicates if the node is pruned or not", + "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", + "getblockchaininforesult-chainwork": "The total cumulative work in the best chain", + "getblockchaininforesult-softforks": "The status of the super-majority soft-forks", + "getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", // SoftForkDescription help. "softforkdescription-reject": "The current activation status of the softfork", @@ -194,6 +191,19 @@ var helpDescsEnUS = map[string]string{ "softforkdescription-id": "The string identifier for the soft fork", "-status": "A bool which indicates if the soft fork is active", + // SoftForks help. + "softforks-softforks": "The status of the super-majority soft-forks", + "softforks-bip9_softforks": "JSON object describing active BIP0009 deployments", + "softforks-bip9_softforks--key": "bip9_softforks", + "softforks-bip9_softforks--value": "An object describing a particular BIP009 deployment", + "softforks-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", + + // UnifiedSoftForks help. + "unifiedsoftforks-softforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", + "unifiedsoftforks-softforks--key": "softforks", + "unifiedsoftforks-softforks--value": "An object describing an active softfork deployment used by bitcoind on or after v0.19.0", + "unifiedsoftforks-softforks--desc": "JSON object describing an active softfork deployment used by bitcoind on or after v0.19.0", + // TxRawResult help. "txrawresult-hex": "Hex-encoded transaction", "txrawresult-txid": "The hash of the transaction", @@ -207,6 +217,7 @@ var helpDescsEnUS = map[string]string{ "txrawresult-blocktime": "Block time in seconds since the 1 Jan 1970 GMT", "txrawresult-size": "The size of the transaction in bytes", "txrawresult-vsize": "The virtual size of the transaction in bytes", + "txrawresult-weight": "The transaction's weight (between vsize*4-3 and vsize*4)", "txrawresult-hash": "The wtxid of the transaction", // SearchRawTransactionsResult help. @@ -223,6 +234,7 @@ var helpDescsEnUS = map[string]string{ "searchrawtransactionsresult-blocktime": "Block time in seconds since the 1 Jan 1970 GMT", "searchrawtransactionsresult-size": "The size of the transaction in bytes", "searchrawtransactionsresult-vsize": "The virtual size of the transaction in bytes", + "searchrawtransactionsresult-weight": "The transaction's weight (between vsize*4-3 and vsize*4)", // GetBlockVerboseResult help. "getblockverboseresult-hash": "The hash of the block (same as provided)", @@ -476,6 +488,7 @@ var helpDescsEnUS = map[string]string{ "getrawmempoolverboseresult-currentpriority": "Current priority", "getrawmempoolverboseresult-depends": "Unconfirmed transactions used as inputs for this transaction", "getrawmempoolverboseresult-vsize": "The virtual size of a transaction", + "getrawmempoolverboseresult-weight": "The transaction's weight (between vsize*4-3 and vsize*4)", // GetRawMempoolCmd help. "getrawmempool--synopsis": "Returns information about all of the transactions currently in the memory pool.", @@ -538,7 +551,8 @@ var helpDescsEnUS = map[string]string{ // SendRawTransactionCmd help. "sendrawtransaction--synopsis": "Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.", "sendrawtransaction-hextx": "Serialized, hex-encoded signed transaction", - "sendrawtransaction-allowhighfees": "Whether or not to allow insanely high fees (ltcd does not yet implement this parameter, so it has no effect)", + "sendrawtransaction-allowhighfees": "Whether or not to allow insanely high fees (btcd does not yet implement this parameter, so it has no effect)", + "sendrawtransaction-maxfeerate": "Used by litecoind on or after v0.19.0", "sendrawtransaction--result0": "The hash of the transaction", // SetGenerateCmd help. diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 4ee4e11ae0..45c4b74b3a 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -20,6 +20,7 @@ import ( "sync" "time" + "github.com/btcsuite/websocket" "github.com/ltcsuite/ltcd/blockchain" "github.com/ltcsuite/ltcd/btcjson" "github.com/ltcsuite/ltcd/chaincfg" @@ -28,7 +29,6 @@ import ( "github.com/ltcsuite/ltcd/txscript" "github.com/ltcsuite/ltcd/wire" "github.com/ltcsuite/ltcutil" - "github.com/btcsuite/websocket" "golang.org/x/crypto/ripemd160" ) @@ -2321,85 +2321,20 @@ func descendantBlock(prevHash *chainhash.Hash, curBlock *ltcutil.Block) error { return nil } -// handleRescan implements the rescan command extension for websocket -// connections. -// -// NOTE: This does not smartly handle reorgs, and fixing requires database -// changes (for safe, concurrent access to full block ranges, and support -// for other chains than the best chain). It will, however, detect whether -// a reorg removed a block that was previously processed, and result in the -// handler erroring. Clients must handle this by finding a block still in -// the chain (perhaps from a rescanprogress notification) to resume their -// rescan. -func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { - cmd, ok := icmd.(*btcjson.RescanCmd) - if !ok { - return nil, btcjson.ErrRPCInternal - } - - outpoints := make([]*wire.OutPoint, 0, len(cmd.OutPoints)) - for i := range cmd.OutPoints { - cmdOutpoint := &cmd.OutPoints[i] - blockHash, err := chainhash.NewHashFromStr(cmdOutpoint.Hash) - if err != nil { - return nil, rpcDecodeHexError(cmdOutpoint.Hash) - } - outpoint := wire.NewOutPoint(blockHash, cmdOutpoint.Index) - outpoints = append(outpoints, outpoint) - } - - numAddrs := len(cmd.Addresses) - if numAddrs == 1 { - rpcsLog.Info("Beginning rescan for 1 address") - } else { - rpcsLog.Infof("Beginning rescan for %d addresses", numAddrs) - } - - // Build lookup maps. - lookups := rescanKeys{ - addrs: map[string]struct{}{}, - unspent: map[wire.OutPoint]struct{}{}, - } - for _, addrStr := range cmd.Addresses { - lookups.addrs[addrStr] = struct{}{} - } - for _, outpoint := range outpoints { - lookups.unspent[*outpoint] = struct{}{} - } - - chain := wsc.server.cfg.Chain - - minBlockHash, err := chainhash.NewHashFromStr(cmd.BeginBlock) - if err != nil { - return nil, rpcDecodeHexError(cmd.BeginBlock) - } - minBlock, err := chain.BlockHeightByHash(minBlockHash) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCBlockNotFound, - Message: "Error getting block: " + err.Error(), - } - } - - maxBlock := int32(math.MaxInt32) - if cmd.EndBlock != nil { - maxBlockHash, err := chainhash.NewHashFromStr(*cmd.EndBlock) - if err != nil { - return nil, rpcDecodeHexError(*cmd.EndBlock) - } - maxBlock, err = chain.BlockHeightByHash(maxBlockHash) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCBlockNotFound, - Message: "Error getting block: " + err.Error(), - } - } - } +// scanBlockChunks executes a rescan in chunked stages. We do this to limit the +// amount of memory that we'll allocate to a given rescan. Every so often, +// we'll send back a rescan progress notification to the websockets client. The +// final block and block hash that we've scanned will be returned. +func scanBlockChunks(wsc *wsClient, cmd *btcjson.RescanCmd, lookups *rescanKeys, minBlock, + maxBlock int32, chain *blockchain.BlockChain) ( + *ltcutil.Block, *chainhash.Hash, error) { // lastBlock and lastBlockHash track the previously-rescanned block. // They equal nil when no previous blocks have been rescanned. - var lastBlock *ltcutil.Block - var lastBlockHash *chainhash.Hash + var ( + lastBlock *ltcutil.Block + lastBlockHash *chainhash.Hash + ) // A ticker is created to wait at least 10 seconds before notifying the // websocket client of the current progress completed by the rescan. @@ -2422,7 +2357,7 @@ fetchRange: hashList, err := chain.HeightRange(minBlock, maxLoopBlock) if err != nil { rpcsLog.Errorf("Error looking up block range: %v", err) - return nil, &btcjson.RPCError{ + return nil, nil, &btcjson.RPCError{ Code: btcjson.ErrRPCDatabase, Message: "Database error: " + err.Error(), } @@ -2461,7 +2396,7 @@ fetchRange: if err != nil { rpcsLog.Errorf("Error fetching best block "+ "hash: %v", err) - return nil, &btcjson.RPCError{ + return nil, nil, &btcjson.RPCError{ Code: btcjson.ErrRPCDatabase, Message: "Database error: " + err.Error(), @@ -2484,7 +2419,7 @@ fetchRange: rpcsLog.Errorf("Error looking up "+ "block: %v", err) - return nil, &btcjson.RPCError{ + return nil, nil, &btcjson.RPCError{ Code: btcjson.ErrRPCDatabase, Message: "Database error: " + err.Error(), @@ -2497,7 +2432,7 @@ fetchRange: rpcsLog.Errorf("Stopping rescan for "+ "reorged block %v", cmd.EndBlock) - return nil, &ErrRescanReorg + return nil, nil, &ErrRescanReorg } // If the lookup for the previously valid block @@ -2511,10 +2446,11 @@ fetchRange: // before the range was evaluated, as it must be // reevaluated for the new hashList. minBlock += int32(i) - hashList, err = recoverFromReorg(chain, - minBlock, maxBlock, lastBlockHash) + hashList, err = recoverFromReorg( + chain, minBlock, maxBlock, lastBlockHash, + ) if err != nil { - return nil, err + return nil, nil, err } if len(hashList) == 0 { break fetchRange @@ -2526,7 +2462,7 @@ fetchRange: // as the last block from the old hashList. jsonErr := descendantBlock(lastBlockHash, blk) if jsonErr != nil { - return nil, jsonErr + return nil, nil, jsonErr } } @@ -2536,9 +2472,9 @@ fetchRange: case <-wsc.quit: rpcsLog.Debugf("Stopped rescan at height %v "+ "for disconnected client", blk.Height()) - return nil, nil + return nil, nil, nil default: - rescanBlock(wsc, &lookups, blk) + rescanBlock(wsc, lookups, blk) lastBlock = blk lastBlockHash = blk.Hash() } @@ -2552,8 +2488,10 @@ fetchRange: continue } - n := btcjson.NewRescanProgressNtfn(hashList[i].String(), - blk.Height(), blk.MsgBlock().Header.Timestamp.Unix()) + n := btcjson.NewRescanProgressNtfn( + hashList[i].String(), blk.Height(), + blk.MsgBlock().Header.Timestamp.Unix(), + ) mn, err := btcjson.MarshalCmd(nil, n) if err != nil { rpcsLog.Errorf("Failed to marshal rescan "+ @@ -2565,23 +2503,140 @@ fetchRange: // Finished if the client disconnected. rpcsLog.Debugf("Stopped rescan at height %v "+ "for disconnected client", blk.Height()) - return nil, nil + return nil, nil, nil } } minBlock += int32(len(hashList)) } - // Notify websocket client of the finished rescan. Due to how ltcd + return lastBlock, lastBlockHash, nil +} + +// handleRescan implements the rescan command extension for websocket +// connections. +// +// NOTE: This does not smartly handle reorgs, and fixing requires database +// changes (for safe, concurrent access to full block ranges, and support +// for other chains than the best chain). It will, however, detect whether +// a reorg removed a block that was previously processed, and result in the +// handler erroring. Clients must handle this by finding a block still in +// the chain (perhaps from a rescanprogress notification) to resume their +// rescan. +func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { + cmd, ok := icmd.(*btcjson.RescanCmd) + if !ok { + return nil, btcjson.ErrRPCInternal + } + + outpoints := make([]*wire.OutPoint, 0, len(cmd.OutPoints)) + for i := range cmd.OutPoints { + cmdOutpoint := &cmd.OutPoints[i] + blockHash, err := chainhash.NewHashFromStr(cmdOutpoint.Hash) + if err != nil { + return nil, rpcDecodeHexError(cmdOutpoint.Hash) + } + outpoint := wire.NewOutPoint(blockHash, cmdOutpoint.Index) + outpoints = append(outpoints, outpoint) + } + + numAddrs := len(cmd.Addresses) + if numAddrs == 1 { + rpcsLog.Info("Beginning rescan for 1 address") + } else { + rpcsLog.Infof("Beginning rescan for %d addresses", numAddrs) + } + + // Build lookup maps. + lookups := rescanKeys{ + addrs: map[string]struct{}{}, + unspent: map[wire.OutPoint]struct{}{}, + } + for _, addrStr := range cmd.Addresses { + lookups.addrs[addrStr] = struct{}{} + } + for _, outpoint := range outpoints { + lookups.unspent[*outpoint] = struct{}{} + } + + chain := wsc.server.cfg.Chain + + minBlockHash, err := chainhash.NewHashFromStr(cmd.BeginBlock) + if err != nil { + return nil, rpcDecodeHexError(cmd.BeginBlock) + } + minBlock, err := chain.BlockHeightByHash(minBlockHash) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCBlockNotFound, + Message: "Error getting block: " + err.Error(), + } + } + + maxBlock := int32(math.MaxInt32) + if cmd.EndBlock != nil { + maxBlockHash, err := chainhash.NewHashFromStr(*cmd.EndBlock) + if err != nil { + return nil, rpcDecodeHexError(*cmd.EndBlock) + } + maxBlock, err = chain.BlockHeightByHash(maxBlockHash) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCBlockNotFound, + Message: "Error getting block: " + err.Error(), + } + } + } + + var ( + lastBlock *ltcutil.Block + lastBlockHash *chainhash.Hash + ) + if len(lookups.addrs) != 0 || len(lookups.unspent) != 0 { + // With all the arguments parsed, we'll execute our chunked rescan + // which will notify the clients of any address deposits or output + // spends. + lastBlock, lastBlockHash, err = scanBlockChunks( + wsc, cmd, &lookups, minBlock, maxBlock, chain, + ) + if err != nil { + return nil, err + } + + // If the last block is nil, then this means that the client + // disconnected mid-rescan. As a result, we don't need to send + // anything back to them. + if lastBlock == nil { + return nil, nil + } + } else { + rpcsLog.Infof("Skipping rescan as client has no addrs/utxos") + + // If we didn't actually do a rescan, then we'll give the + // client our best known block within the final rescan finished + // notification. + chainTip := chain.BestSnapshot() + lastBlockHash = &chainTip.Hash + lastBlock, err = chain.BlockByHash(lastBlockHash) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCBlockNotFound, + Message: "Error getting block: " + err.Error(), + } + } + } + + // Notify websocket client of the finished rescan. Due to how btcd // asynchronously queues notifications to not block calling code, // there is no guarantee that any of the notifications created during // rescan (such as rescanprogress, recvtx and redeemingtx) will be // received before the rescan RPC returns. Therefore, another method // is needed to safely inform clients that all rescan notifications have // been sent. - n := btcjson.NewRescanFinishedNtfn(lastBlockHash.String(), - lastBlock.Height(), - lastBlock.MsgBlock().Header.Timestamp.Unix()) + n := btcjson.NewRescanFinishedNtfn( + lastBlockHash.String(), lastBlock.Height(), + lastBlock.MsgBlock().Header.Timestamp.Unix(), + ) if mn, err := btcjson.MarshalCmd(nil, n); err != nil { rpcsLog.Errorf("Failed to marshal rescan finished "+ "notification: %v", err) diff --git a/server.go b/server.go index dbe5aaecaf..5bca21c308 100644 --- a/server.go +++ b/server.go @@ -273,6 +273,7 @@ type serverPeer struct { sentAddrs bool isWhitelisted bool filter *bloom.Filter + addressesMtx sync.RWMutex knownAddresses map[string]struct{} banScore connmgr.DynamicBanScore quit chan struct{} @@ -305,14 +306,18 @@ func (sp *serverPeer) newestBlock() (*chainhash.Hash, int32, error) { // addKnownAddresses adds the given addresses to the set of known addresses to // the peer to prevent sending duplicate addresses. func (sp *serverPeer) addKnownAddresses(addresses []*wire.NetAddress) { + sp.addressesMtx.Lock() for _, na := range addresses { sp.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} } + sp.addressesMtx.Unlock() } // addressKnown true if the given address is already known to the peer. func (sp *serverPeer) addressKnown(na *wire.NetAddress) bool { + sp.addressesMtx.RLock() _, exists := sp.knownAddresses[addrmgr.NetAddressKey(na)] + sp.addressesMtx.RUnlock() return exists } @@ -438,11 +443,6 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej return wire.NewMsgReject(msg.Command(), wire.RejectNonstandard, reason) } - // Update the address manager and request known addresses from the - // remote peer for outbound connections. This is skipped when running - // on the simulation test network since it is only intended to connect - // to specified peers and actively avoids advertising and connecting to - // discovered peers. if !cfg.SimNet && !isInbound { // After soft-fork activation, only make outbound // connection to peers if they flag that they're segwit @@ -461,47 +461,25 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej sp.Disconnect() return nil } - - // Advertise the local address when the server accepts incoming - // connections and it believes itself to be close to the best known tip. - if !cfg.DisableListen && sp.server.syncManager.IsCurrent() { - // Get address that best matches. - lna := addrManager.GetBestLocalAddress(remoteAddr) - if addrmgr.IsRoutable(lna) { - // Filter addresses the peer already knows about. - addresses := []*wire.NetAddress{lna} - sp.pushAddrMsg(addresses) - } - } - - // Request known addresses if the server address manager needs - // more and the peer has a protocol version new enough to - // include a timestamp with addresses. - hasTimestamp := sp.ProtocolVersion() >= wire.NetAddressTimeVersion - if addrManager.NeedMoreAddresses() && hasTimestamp { - sp.QueueMessage(wire.NewMsgGetAddr(), nil) - } - - // Mark the address as a known good address. - addrManager.Good(remoteAddr) } // Add the remote peer time as a sample for creating an offset against // the local clock to keep the network time in sync. sp.server.timeSource.AddTimeSample(sp.Addr(), msg.Timestamp) - // Signal the sync manager this peer is a new sync candidate. - sp.server.syncManager.NewPeer(sp.Peer) - // Choose whether or not to relay transactions before a filter command // is received. sp.setDisableRelayTx(msg.DisableRelayTx) - // Add valid peer to the server. - sp.server.AddPeer(sp) return nil } +// OnVerAck is invoked when a peer receives a verack bitcoin message and is used +// to kick start communication with them. +func (sp *serverPeer) OnVerAck(_ *peer.Peer, _ *wire.MsgVerAck) { + sp.server.AddPeer(sp) +} + // OnMemPool is invoked when a peer receives a mempool bitcoin message. // It creates and sends an inventory message with the contents of the memory // pool up to the maximum inventory allowed per message. When the peer has a @@ -1590,7 +1568,7 @@ func (s *server) handleUpdatePeerHeights(state *peerState, umsg updatePeerHeight // handleAddPeerMsg deals with adding new peers. It is invoked from the // peerHandler goroutine. func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { - if sp == nil { + if sp == nil || !sp.Connected() { return false } @@ -1651,6 +1629,46 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { } } + // Update the address' last seen time if the peer has acknowledged + // our version and has sent us its version as well. + if sp.VerAckReceived() && sp.VersionKnown() && sp.NA() != nil { + s.addrManager.Connected(sp.NA()) + } + + // Signal the sync manager this peer is a new sync candidate. + s.syncManager.NewPeer(sp.Peer) + + // Update the address manager and request known addresses from the + // remote peer for outbound connections. This is skipped when running on + // the simulation test network since it is only intended to connect to + // specified peers and actively avoids advertising and connecting to + // discovered peers. + if !cfg.SimNet && !sp.Inbound() { + // Advertise the local address when the server accepts incoming + // connections and it believes itself to be close to the best + // known tip. + if !cfg.DisableListen && s.syncManager.IsCurrent() { + // Get address that best matches. + lna := s.addrManager.GetBestLocalAddress(sp.NA()) + if addrmgr.IsRoutable(lna) { + // Filter addresses the peer already knows about. + addresses := []*wire.NetAddress{lna} + sp.pushAddrMsg(addresses) + } + } + + // Request known addresses if the server address manager needs + // more and the peer has a protocol version new enough to + // include a timestamp with addresses. + hasTimestamp := sp.ProtocolVersion() >= wire.NetAddressTimeVersion + if s.addrManager.NeedMoreAddresses() && hasTimestamp { + sp.QueueMessage(wire.NewMsgGetAddr(), nil) + } + + // Mark the address as a known good address. + s.addrManager.Good(sp.NA()) + } + return true } @@ -1665,30 +1683,27 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { } else { list = state.outboundPeers } + + // Regardless of whether the peer was found in our list, we'll inform + // our connection manager about the disconnection. This can happen if we + // process a peer's `done` message before its `add`. + if !sp.Inbound() { + if sp.persistent { + s.connManager.Disconnect(sp.connReq.ID()) + } else { + s.connManager.Remove(sp.connReq.ID()) + go s.connManager.NewConnReq() + } + } + if _, ok := list[sp.ID()]; ok { if !sp.Inbound() && sp.VersionKnown() { state.outboundGroups[addrmgr.GroupKey(sp.NA())]-- } - if !sp.Inbound() && sp.connReq != nil { - s.connManager.Disconnect(sp.connReq.ID()) - } delete(list, sp.ID()) srvrLog.Debugf("Removed peer %s", sp) return } - - if sp.connReq != nil { - s.connManager.Disconnect(sp.connReq.ID()) - } - - // Update the address' last seen time if the peer has acknowledged - // our version and has sent us its version as well. - if sp.VerAckReceived() && sp.VersionKnown() && sp.NA() != nil { - s.addrManager.Connected(sp.NA()) - } - - // If we get here it means that either we didn't know about the peer - // or we purposefully deleted it. } // handleBanPeerMsg deals with banning peers. It is invoked from the @@ -1963,6 +1978,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config { return &peer.Config{ Listeners: peer.MessageListeners{ OnVersion: sp.OnVersion, + OnVerAck: sp.OnVerAck, OnMemPool: sp.OnMemPool, OnTx: sp.OnTx, OnBlock: sp.OnBlock, @@ -2025,14 +2041,19 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) { p, err := peer.NewOutboundPeer(newPeerConfig(sp), c.Addr.String()) if err != nil { srvrLog.Debugf("Cannot create outbound peer %s: %v", c.Addr, err) - s.connManager.Disconnect(c.ID()) + if c.Permanent { + s.connManager.Disconnect(c.ID()) + } else { + s.connManager.Remove(c.ID()) + go s.connManager.NewConnReq() + } + return } sp.Peer = p sp.connReq = c sp.isWhitelisted = isWhitelisted(conn.RemoteAddr()) sp.AssociateConnection(conn) go s.peerDoneHandler(sp) - s.addrManager.Attempt(sp.NA()) } // peerDoneHandler handles peer disconnects by notifiying the server that it's @@ -2042,7 +2063,7 @@ func (s *server) peerDoneHandler(sp *serverPeer) { s.donePeers <- sp // Only tell sync manager we are gone if we ever told it we existed. - if sp.VersionKnown() { + if sp.VerAckReceived() { s.syncManager.DonePeer(sp.Peer) // Evict any remaining orphans that were sent by the peer. @@ -2802,6 +2823,9 @@ func newServer(listenAddrs, agentBlacklist, agentWhitelist []string, continue } + // Mark an attempt for the valid address. + s.addrManager.Attempt(addr.NetAddress()) + addrString := addrmgr.NetAddressKey(addr.NetAddress()) return addrStringToNetAddr(addrString) } diff --git a/upnp.go b/upnp.go index a1f37e6507..feb256d072 100644 --- a/upnp.go +++ b/upnp.go @@ -298,7 +298,6 @@ func soapRequest(url, function, message string) (replyXML []byte, err error) { } req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - //req.Header.Set("Transfer-Encoding", "chunked") req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"") req.Header.Set("Connection", "Close") req.Header.Set("Cache-Control", "no-cache") @@ -313,7 +312,6 @@ func soapRequest(url, function, message string) (replyXML []byte, err error) { } if r.StatusCode >= 400 { - // log.Stderr(function, r.StatusCode) err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) r = nil return diff --git a/version.go b/version.go index fb147bcda5..f65cacef7e 100644 --- a/version.go +++ b/version.go @@ -17,8 +17,8 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr // versioning 2.0.0 spec (http://semver.org/). const ( appMajor uint = 0 - appMinor uint = 12 - appPatch uint = 0 + appMinor uint = 20 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. diff --git a/wire/message.go b/wire/message.go index 52b05cecdf..eed9203690 100644 --- a/wire/message.go +++ b/wire/message.go @@ -321,9 +321,13 @@ func WriteMessageWithEncodingN(w io.Writer, msg Message, pver uint32, return totalBytes, err } - // Write payload. - n, err = w.Write(payload) - totalBytes += n + // Only write the payload if there is one, e.g., verack messages don't + // have one. + if len(payload) > 0 { + n, err = w.Write(payload) + totalBytes += n + } + return totalBytes, err }