From a942799999d1088caeecf64205427c56444035f3 Mon Sep 17 00:00:00 2001 From: Jasper Date: Mon, 16 Apr 2018 06:38:35 +0200 Subject: [PATCH 01/34] Update README.md Use internet archive for old conformal blog entry link. --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index c00d295e7a..cb6517b186 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,7 +47,7 @@ transactions based on miner requirements ("standard" transactions). One key difference between btcd and Bitcoin Core is that btcd 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 btcd. That functionality is provided by the [btcwallet](https://github.com/btcsuite/btcwallet) and From 594e2ab48a8e309c910c4fee7ce73a529e26aa52 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 28 Jun 2019 15:43:47 -0700 Subject: [PATCH 02/34] rpc: extract primary rescan logic into scanBlockChunks helper func --- rpcwebsocket.go | 211 +++++++++++++++++++++++++++--------------------- 1 file changed, 119 insertions(+), 92 deletions(-) diff --git a/rpcwebsocket.go b/rpcwebsocket.go index eddac5ab06..85e1eaa36e 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -2321,85 +2321,20 @@ func descendantBlock(prevHash *chainhash.Hash, curBlock *btcutil.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) ( + *btcutil.Block, *chainhash.Hash, error) { // lastBlock and lastBlockHash track the previously-rescanned block. // They equal nil when no previous blocks have been rescanned. - var lastBlock *btcutil.Block - var lastBlockHash *chainhash.Hash + var ( + lastBlock *btcutil.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,13 +2503,101 @@ 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)) } + 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(), + } + } + } + + // 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 + } + // 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 @@ -2579,9 +2605,10 @@ fetchRange: // 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) From 18c37d2eb7b5a0102f36237b67e43f41700bddca Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 28 Jun 2019 15:45:20 -0700 Subject: [PATCH 03/34] rpc: skip rescan if client has no addresses or UTXOs In this commit, we implement an optimization that will speed up clients that attempt to perform a rescan in the past with no addresses. If the client doesn't have any thing to search for, then we simply exit early and send them a rescan finished notification with the final block in our chain. --- rpcwebsocket.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 85e1eaa36e..b4bdadbc9a 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -2588,6 +2588,11 @@ func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { } } + var ( + lastBlock *btcutil.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. @@ -2597,6 +2602,22 @@ func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { if err != nil { return nil, err } + } 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, From 677503515ebfc855ce80a6a126c276a672877893 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sun, 7 Jul 2019 18:03:04 -0700 Subject: [PATCH 04/34] rpc: fix rescan bug if client disconnects In this commit, we fix a bug in the rescan logic after a recent refactoring that would cause the scanning node to potentially panic. If the client disconnected, before the rescan was finished, then we would return a nil `lastBlock` and `lastBlockHash`. We would then attempt to send the rescan finished notification, causing a panic. We remedy this by simply detecting this case (client disconnect), and existing once again as the prior code would, pre-refactoring. --- rpcwebsocket.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rpcwebsocket.go b/rpcwebsocket.go index b4bdadbc9a..32e466d115 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -2602,6 +2602,13 @@ func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { 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") From 2cd83210b57d499665fc6d2951983334996bf7fe Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 20 Aug 2019 15:26:23 -0700 Subject: [PATCH 05/34] wire: only write message payload if there actually is one There are certain messages that won't have a payload, e.g., verack, causing an unnecessary write of 0 bytes. --- wire/message.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wire/message.go b/wire/message.go index 4f03cf5643..e937647702 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 } From 5de9e5b6c2a415bc3f5d0c86951699dd70e3f4a1 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 20 Aug 2019 15:28:42 -0700 Subject: [PATCH 06/34] peer: include verack as part of version handshake It was possible for connections to bitcoind nodes to be closed from their side if the OnVersion callback queued a message to send. This is because bitcoind expects a verack message before attempting to process any other messages. To address this, we ensure the verack is sent as part of the handshake process, such that any queued messages happen after the fact. --- peer/peer.go | 99 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 61bc619ffb..11306ace49 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1377,20 +1377,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 { @@ -1974,6 +1966,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) { @@ -2046,26 +2072,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. @@ -2102,8 +2155,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 } From e3d3088b80acb3d7943d38c495525d013a564892 Mon Sep 17 00:00:00 2001 From: preminem <31085564+preminem@users.noreply.github.com> Date: Thu, 26 Sep 2019 08:19:03 +0800 Subject: [PATCH 07/34] btcjson+rpc: expose a transaction's weight via RPC --- btcjson/chainsvrresults.go | 3 +++ docs/json_rpc_api.md | 2 +- mempool/mempool.go | 1 + rpcserver.go | 1 + rpcserverhelp.go | 3 +++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 30c6369a32..cc9c085531 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -265,6 +265,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 +506,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 +525,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/docs/json_rpc_api.md b/docs/json_rpc_api.md index 9b8b36b3bb..51d4c82c3f 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 btcd 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/mempool/mempool.go b/mempool/mempool.go index 35eaf23414..63a86a16f5 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: btcutil.Amount(desc.Fee).ToBTC(), Time: desc.Added.Unix(), Height: int64(desc.Height), diff --git a/rpcserver.go b/rpcserver.go index a3cf1e0f7a..097ba2ec84 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(btcutil.NewTx(mtx))), + Weight: int32(blockchain.GetTransactionWeight(btcutil.NewTx(mtx))), Vin: createVinList(mtx), Vout: createVoutList(mtx, chainParams, nil), Version: mtx.Version, diff --git a/rpcserverhelp.go b/rpcserverhelp.go index c875d217aa..e7637db69c 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -207,6 +207,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 +224,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 +478,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.", From 009f1993178a97d7466ff500fae086e4425676f1 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Thu, 26 Sep 2019 03:23:57 +0300 Subject: [PATCH 08/34] btcd: remove commented-out code Found using https://go-critic.github.io/overview#commentedOutCode-ref --- upnp.go | 2 -- 1 file changed, 2 deletions(-) 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 From ba530c4abb35ea824a1d3c01d74969b5564c3b08 Mon Sep 17 00:00:00 2001 From: Sad Pencil Date: Thu, 26 Sep 2019 08:28:57 +0800 Subject: [PATCH 09/34] btcec: correct the comment of recoverKeyFromSignature --- btcec/signature.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e159f05c6e40ccdf1176199767f4273ff32e549d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 25 Sep 2019 18:43:43 -0700 Subject: [PATCH 10/34] build: use go 1.13.x in travis --- .travis.yml | 2 +- go.mod | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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/go.mod b/go.mod index 9488a8bd39..606399481f 100644 --- a/go.mod +++ b/go.mod @@ -17,3 +17,5 @@ require ( github.com/onsi/gomega v1.4.3 // indirect golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 ) + +go 1.12 From 4aeb189fc41791cb29945ed710f0d529b61e1aac Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 14 May 2019 22:46:50 -0700 Subject: [PATCH 11/34] btcec: benchmark ParsePubKey for compressed keys --- btcec/bench_test.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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 +} From 39500ed5ed1904c5b1a6182b98913bbedee0b412 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 14 May 2019 22:46:54 -0700 Subject: [PATCH 12/34] btcec/pubkey: remove redundant checks from compressed pubkey parsing As of https://github.com/btcsuite/btcd/pull/1193, decompressPoint now validates that the point is on the curve. The x and y cooordinates are also implicitly <= P, since the modular reduction is applied to both before the method returns. The checks are moved so that they are still applied when parsing an uncompressed pubkey, as the checks are not redundant in that path. --- btcec/pubkey.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/btcec/pubkey.go b/btcec/pubkey.go index cf49807522..a6a492e7c3 100644 --- a/btcec/pubkey.go +++ b/btcec/pubkey.go @@ -102,6 +102,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 +126,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 } From c7d523f83ccb19ea4766e6523685105bd6c76e70 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 14 May 2019 22:46:59 -0700 Subject: [PATCH 13/34] btcec/pubkey: optimize decompressPoint using fieldVals This commit optimizes the decompressPoint subroutine, used in extracting compressed pubkeys and performing pubkey recovery. We do so by replacing the use of big.Int.Exp with with square-and-multiply exponentiation of btcec's more optimized fieldVals, reducing the overall latency and memory requirements of decompressPoint. Instead of operating on bits of Q = (P+1)/4, the exponentiation applies the square-and-multiply operations on full bytes of Q. Compared to the original speedup. Compared the bit-wise version, the improvement is roughly 10%. A new pair fieldVal methods called Sqrt and SqrtVal are added, which applies the square-and-multiply exponentiation using precomputed byte-slice of the value Q. Comparison against big.Int sqrt and SAM sqrt over bytes of Q: benchmark old ns/op new ns/op delta BenchmarkParseCompressedPubKey-8 35545 23119 -34.96% benchmark old allocs new allocs delta BenchmarkParseCompressedPubKey-8 35 6 -82.86% benchmark old bytes new bytes delta BenchmarkParseCompressedPubKey-8 2777 256 -90.78% --- btcec/btcec.go | 10 ++- btcec/field.go | 129 +++++++++++++++++++++++++++++++++++++++ btcec/field_test.go | 145 ++++++++++++++++++++++++++++++++++++++++++++ btcec/pubkey.go | 36 +++++------ 4 files changed, 301 insertions(+), 19 deletions(-) diff --git a/btcec/btcec.go b/btcec/btcec.go index 5e7ce875fd..2cb1d92913 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 @@ -917,6 +924,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 a6a492e7c3..c72f8705ab 100644 --- a/btcec/pubkey.go +++ b/btcec/pubkey.go @@ -22,41 +22,41 @@ 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) + if ybit != y.IsOdd() { + y.Negate(1) } + y.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 ( From 2340ad388c7179de515dec1c58ce9c92f0825f91 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 14 May 2019 22:47:03 -0700 Subject: [PATCH 14/34] btcec/btcec: deprecate QPlus1Div4() in favor of Q() The previous naming suggested that the value ((P+1)/4+1)/4 was being returned, when in fact the returned value is simply (P+1)/4. The old method is superseded by Q(). --- btcec/btcec.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/btcec/btcec.go b/btcec/btcec.go index 2cb1d92913..de93a255a4 100644 --- a/btcec/btcec.go +++ b/btcec/btcec.go @@ -886,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 From 06baabe5da28d17a1dd357594e2b8f5cc7ea3091 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 10 Oct 2019 12:03:47 -0400 Subject: [PATCH 15/34] server: mark address attempted on attempt rather than upon connection We should mark addresses as attempted when we attempt to connect to them, not once we establish a connection with said address. --- server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index bed4de9593..7d5da9c1d7 100644 --- a/server.go +++ b/server.go @@ -2032,7 +2032,6 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) { 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 @@ -2802,6 +2801,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) } From ab6f3089f667f38172d9b45936206f0b4bc6f215 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 10 Oct 2019 12:10:52 -0400 Subject: [PATCH 16/34] server: mark address as connected within handleAddPeerMsg We do this to ensure the address manager contains live addresses. Previously, addresses with which we established connections with would not be marked as connected because it would be done once we disconnect peers. Given that we don't process all of the disconnect logic when we're shutting down, addresses of stable and good peers would never be marked as connected unless the connection was lost during operation. --- server.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server.go b/server.go index 7d5da9c1d7..eaaa1133ab 100644 --- a/server.go +++ b/server.go @@ -1651,6 +1651,12 @@ 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()) + } + return true } @@ -1681,12 +1687,6 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { 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. } From e47518c978d8d22d987a64a8eecbc305c693fd4e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 10 Oct 2019 15:54:11 -0700 Subject: [PATCH 17/34] btcd: bump version to v0.20.0-beta --- cmd/btcctl/version.go | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/btcctl/version.go b/cmd/btcctl/version.go index fb147bcda5..2195175c71 100644 --- a/cmd/btcctl/version.go +++ b/cmd/btcctl/version.go @@ -17,7 +17,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr // versioning 2.0.0 spec (http://semver.org/). const ( appMajor uint = 0 - appMinor uint = 12 + appMinor uint = 20 appPatch uint = 0 // appPreRelease MUST only contain characters from semanticAlphabet diff --git a/version.go b/version.go index 192f4a7f58..92fd60fdd4 100644 --- a/version.go +++ b/version.go @@ -17,7 +17,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr // versioning 2.0.0 spec (http://semver.org/). const ( appMajor uint = 0 - appMinor uint = 12 + appMinor uint = 20 appPatch uint = 0 // appPreRelease MUST only contain characters from semanticAlphabet From 45d66d46f9c5c917b643eab43a061f35157ece6b Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 10 Oct 2019 13:18:03 -0400 Subject: [PATCH 18/34] server: standardize use of connmanager's Disconnect and Remove methods The Disconnect method would still attempt to reconnect to the same peer, which could cause us to reconnect to bad/unstable peers if we came across them. Instead, we'll now use Remove whenever we intend to remove a peer that is not persistent. --- server.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index eaaa1133ab..b518a66303 100644 --- a/server.go +++ b/server.go @@ -1671,24 +1671,26 @@ 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()) + } + } + 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()) - } - - // 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 @@ -2025,7 +2027,12 @@ 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()) + } + return } sp.Peer = p sp.connReq = c From 0d00cdf82c3266f014ec7ef7a7717daf28e69de6 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 10 Oct 2019 13:25:19 -0400 Subject: [PATCH 19/34] server: request new peer after disconnection of non-persistent peers Doing so ensures we reach our target number of outbound peers as soon as possible. This is only necessary after calls to connmgr.Remove, as these won't request a new peer connection. --- server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.go b/server.go index b518a66303..9f4c54d3e4 100644 --- a/server.go +++ b/server.go @@ -1680,6 +1680,7 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { s.connManager.Disconnect(sp.connReq.ID()) } else { s.connManager.Remove(sp.connReq.ID()) + go s.connManager.NewConnReq() } } @@ -2031,6 +2032,7 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) { s.connManager.Disconnect(c.ID()) } else { s.connManager.Remove(c.ID()) + go s.connManager.NewConnReq() } return } From 2083acdd247338336bf1a1b644b53be2960131aa Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 10 Oct 2019 16:17:57 -0700 Subject: [PATCH 20/34] release: add new release script and documentation In this commit, we add the new release script that will be used to build all release binaries going forward. We also remove the existing Conformal key as it's no longer in use, updating the README to reflect the new release build/verification process. --- README.md | 25 ++----- release/GIT-GPG-KEY-conformal.txt | 74 -------------------- release/README.md | 71 +++++++++++++++++++ release/release.sh | 109 ++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 93 deletions(-) delete mode 100644 release/GIT-GPG-KEY-conformal.txt create mode 100644 release/README.md create mode 100755 release/release.sh diff --git a/README.md b/README.md index 2b126a6503..a270fd2e97 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ which are both under active development. ## Requirements -[Go](http://golang.org) 1.11 or newer. +[Go](http://golang.org) 1.12 or newer. ## Installation @@ -118,25 +118,12 @@ is used for this project. The documentation is a work-in-progress. It is located in the [docs](https://github.com/btcsuite/btcd/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 btcsuite developers. To -verify the signature perform the following: - -- Download the Conformal public key: - https://raw.githubusercontent.com/btcsuite/btcd/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/btcsuite/btcd/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/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..53c73b8d1e --- /dev/null +++ b/release/release.sh @@ -0,0 +1,109 @@ +#!/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 + windows-arm +"} + +# 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 From 069ec701df22480be2a7d820f5a2095e8e38b19c Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 10 Oct 2019 18:00:37 -0700 Subject: [PATCH 21/34] btcec/pubkey: normalize sqrt(x^3) before checking parity This commit fixes an issue introduced in the recent #1429, where the output of SqrtVal is not normalized before using IsOdd() to compare with the expected parity of the y-coordinate. The IsOdd() is only guaranteed to work if the value has been denormalized, so a denormalized sqrt >= p would report the opposite parity. We fix this by normalizing both after compute sqrt(x^3) and when negating the root as directed by the ybit. --- btcec/pubkey.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/btcec/pubkey.go b/btcec/pubkey.go index c72f8705ab..3c9d5d02d2 100644 --- a/btcec/pubkey.go +++ b/btcec/pubkey.go @@ -38,11 +38,10 @@ func decompressPoint(curve *KoblitzCurve, bigX *big.Int, ybit bool) (*big.Int, e // but this was replaced by the algorithms referenced in // https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294 var y fieldVal - y.SqrtVal(&x3) + y.SqrtVal(&x3).Normalize() if ybit != y.IsOdd() { - y.Negate(1) + y.Negate(1).Normalize() } - y.Normalize() // Check that y is a square root of x^3 + B. var y2 fieldVal From 11b84f5cb57a9edc1a86109344636b5598df192d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 11 Oct 2019 18:17:21 -0400 Subject: [PATCH 22/34] server: signal SyncManager with new peer within AddPeer This makes the logic a bit more unified as previously it was possible we for us to report the new peer to the SyncManager, but for whatever reason failed to track the peer in the server internally within AddPeer. This change ensures this can no longer happen. --- server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index 9f4c54d3e4..17dc278348 100644 --- a/server.go +++ b/server.go @@ -490,9 +490,6 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej // 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) @@ -1657,6 +1654,9 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { s.addrManager.Connected(sp.NA()) } + // Signal the sync manager this peer is a new sync candidate. + s.syncManager.NewPeer(sp.Peer) + return true } From a1a5bfa81957965f2b65ba661bd8632d25be6a64 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 11 Oct 2019 18:19:59 -0400 Subject: [PATCH 23/34] server: add new peers within OnVerAck instead of within OnVersion This change is needed as part of requiring peers to also send a verack message following their version message during protocol negotiation. Peers were previously added to the SyncManager before their message queues were started, causing the server to stall if a peer didn't provide a timely verack response following their version. We now do this within OnVerAck, which happens shortly before peer message queues are started. --- server.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 17dc278348..bff032d9a7 100644 --- a/server.go +++ b/server.go @@ -494,11 +494,15 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej // 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 @@ -1966,6 +1970,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, From 769c4e152f0711aba7560a5d68005b0779b5f677 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 11 Oct 2019 18:24:31 -0400 Subject: [PATCH 24/34] server: request addresses from new peers once they can process messages This was previously done within the OnVersion listener, which should not be a blocking operation. It turns out that requesting these messages there can lead to blocking due to peers not being able to process messages since their message queues have yet to start. Therefore, we'll now request within handleAddPeerMsg, which should allow it to go through. --- server.go | 59 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/server.go b/server.go index bff032d9a7..630937fb39 100644 --- a/server.go +++ b/server.go @@ -438,11 +438,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,29 +456,6 @@ 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 @@ -1661,6 +1633,37 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { // 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 } From baeb789a7d789cf095b6c465c2a5754cf6e4dff7 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 11 Oct 2019 18:29:09 -0400 Subject: [PATCH 25/34] server: prevent adding peers if already disconnected This addresses an issue where the server ends up tracking a peer that has been disconnected due to it processing a peer's `done` message before its `add` message. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 630937fb39..1d7fdf9b71 100644 --- a/server.go +++ b/server.go @@ -1563,7 +1563,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 } From a41498d578a99c1619037746abd916176ea61052 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 15 Oct 2019 10:11:03 +0200 Subject: [PATCH 26/34] release: remove windows-arm --- release/release.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/release/release.sh b/release/release.sh index 53c73b8d1e..7b0885dac6 100755 --- a/release/release.sh +++ b/release/release.sh @@ -68,7 +68,6 @@ SYS=${BTCDBUILDSYS:-" solaris-amd64 windows-386 windows-amd64 - windows-arm "} # Use the first element of $GOPATH in the case where GOPATH is a list From a46f7b45abb3512c0a43c3c7f268162c48160a96 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:49:37 -0700 Subject: [PATCH 27/34] rpcclient: add GetNetworkInfo method --- rpcclient/net.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/rpcclient/net.go b/rpcclient/net.go index 3b166cb934..6eb7362541 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 From bc21593480f27e1cdb320c5bbdfde3a382dc4b07 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 19:38:38 -0700 Subject: [PATCH 28/34] server: remove peer from SyncManager on VerAckReceived Peers are now added to the SyncManager if we receive their verack, but we'd still attempt to remove them from the SyncManager if we didn't receive it. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 1d7fdf9b71..a0fc8099ec 100644 --- a/server.go +++ b/server.go @@ -2058,7 +2058,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. From e89d4fca24a0dee0d89f913c54412f1e6635b1ff Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:50:11 -0700 Subject: [PATCH 29/34] rpcclient: allow retrieval of backend version --- rpcclient/infrastructure.go | 109 ++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 27f7f21f1b..7a8f1885d1 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 +} From 266851e329e2089fd2e0f082ddc6002c920be879 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:50:46 -0700 Subject: [PATCH 30/34] btcjson+rpcclient: support new unified softfork bitcoind format --- btcjson/chainsvrresults.go | 48 ++++++++++++++----- integration/bip0009_test.go | 2 +- rpcclient/chain.go | 67 ++++++++++++++++++++++++--- rpcclient/chain_test.go | 92 +++++++++++++++++++++++++++++++++++++ rpcserver.go | 8 ++-- rpcserverhelp.go | 40 ++++++++++------ 6 files changed, 220 insertions(+), 37 deletions(-) create mode 100644 rpcclient/chain_test.go diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index cc9c085531..257a6c90c3 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -97,21 +97,45 @@ type Bip9SoftForkDescription struct { Since int32 `json:"since"` } +// 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 diff --git a/integration/bip0009_test.go b/integration/bip0009_test.go index 181c8983ef..df3721b1e6 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/rpcclient/chain.go b/rpcclient/chain.go index c21668918d..996d80458c 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/rpcserver.go b/rpcserver.go index 097ba2ec84..0a7ab8ed89 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1198,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, @@ -1281,7 +1283,7 @@ 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{ + chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ Status: strings.ToLower(statusString), Bit: deploymentDetails.BitNumber, StartTime: int64(deploymentDetails.StartTime), diff --git a/rpcserverhelp.go b/rpcserverhelp.go index e7637db69c..7da266eac1 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", From 93a84aa0143f6bc0787891ab2fe9b0beb36f4b22 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:51:12 -0700 Subject: [PATCH 31/34] btcjson: use correct json tag for Bip9SoftForkDescription.StartTime --- btcjson/chainsvrresults.go | 19 ++++++++++++++----- rpcserver.go | 8 ++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 257a6c90c3..7e6c710766 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -90,11 +90,20 @@ 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 diff --git a/rpcserver.go b/rpcserver.go index 0a7ab8ed89..e762cc1a9d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1284,10 +1284,10 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str // Finally, populate the soft-fork description with all the // information gathered above. chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ - Status: strings.ToLower(statusString), - Bit: deploymentDetails.BitNumber, - StartTime: int64(deploymentDetails.StartTime), - Timeout: int64(deploymentDetails.ExpireTime), + Status: strings.ToLower(statusString), + Bit: deploymentDetails.BitNumber, + StartTime2: int64(deploymentDetails.StartTime), + Timeout: int64(deploymentDetails.ExpireTime), } } From e2e5cc694dda523225cc01166867411ecb6c8477 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:51:59 -0700 Subject: [PATCH 32/34] btcjson+rpcclient: support new bitcoind sendrawtransaction request --- btcjson/chainsvrcmds.go | 12 ++++++++++++ rpcclient/rawtransactions.go | 32 +++++++++++++++++++++++++++++++- rpcserverhelp.go | 1 + 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index a5e5647112..406357bd06 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/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index e886f2250d..4bf4ee57fc 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -15,6 +15,12 @@ import ( "github.com/btcsuite/btcutil" ) +const ( + // defaultMaxFeeRate is the default maximum fee rate in sat/KB enforced + // by bitcoind v0.19.0 or after for transaction broadcast. + defaultMaxFeeRate = btcutil.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/rpcserverhelp.go b/rpcserverhelp.go index 7da266eac1..cc6935b512 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -552,6 +552,7 @@ var helpDescsEnUS = map[string]string{ "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 (btcd does not yet implement this parameter, so it has no effect)", + "sendrawtransaction-maxfeerate": "Used by bitcoind on or after v0.19.0", "sendrawtransaction--result0": "The hash of the transaction", // SetGenerateCmd help. From c2ca0a408e09db7f46b760c3cb8a280ede58d54a Mon Sep 17 00:00:00 2001 From: nsa Date: Fri, 8 Nov 2019 21:11:24 -0500 Subject: [PATCH 33/34] server: add addressesMtx to fix race condition --- server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.go b/server.go index 1d7fdf9b71..786d9b2a2c 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 } From f3ec13030e4e828869954472cbc51ac36bee5c1d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 12 Nov 2019 18:27:49 -0800 Subject: [PATCH 34/34] btcd: bump version to v0.20.1-beta --- cmd/btcctl/version.go | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/btcctl/version.go b/cmd/btcctl/version.go index 2195175c71..f65cacef7e 100644 --- a/cmd/btcctl/version.go +++ b/cmd/btcctl/version.go @@ -18,7 +18,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 20 - appPatch uint = 0 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. diff --git a/version.go b/version.go index 92fd60fdd4..fba55b5a37 100644 --- a/version.go +++ b/version.go @@ -18,7 +18,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 20 - appPatch uint = 0 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec.