From 8836219a0202e6d7f58d4d6d8be9fd1b4c615638 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Fri, 16 Feb 2024 16:15:27 +0900 Subject: [PATCH 01/33] blockchain: return error in db.View instead of t.Fatal Calling t.Fatal inside db.View makes the test hang on failures. Return the error and call t.Fatal outside of db.View avoids this. --- blockchain/utxocache_test.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index 45116655e3..64d517b09a 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -749,36 +749,40 @@ func TestFlushOnPrune(t *testing.T) { ffldb.TstRunWithMaxBlockFileSize(chain.db, maxBlockFileSize, syncBlocks) // Function that errors out if the block that should exist doesn't exist. - shouldExist := func(dbTx database.Tx, blockHash *chainhash.Hash) { + shouldExist := func(dbTx database.Tx, blockHash *chainhash.Hash) error { bytes, err := dbTx.FetchBlock(blockHash) if err != nil { - t.Fatal(err) + return err } block, err := btcutil.NewBlockFromBytes(bytes) if err != nil { - t.Fatalf("didn't find block %v. %v", blockHash, err) + return fmt.Errorf("didn't find block %v. %v", blockHash, err) } if !block.Hash().IsEqual(blockHash) { - t.Fatalf("expected to find block %v but got %v", + return fmt.Errorf("expected to find block %v but got %v", blockHash, block.Hash()) } + + return nil } // Function that errors out if the block that shouldn't exist exists. - shouldNotExist := func(dbTx database.Tx, blockHash *chainhash.Hash) { + shouldNotExist := func(dbTx database.Tx, blockHash *chainhash.Hash) error { bytes, err := dbTx.FetchBlock(chaincfg.MainNetParams.GenesisHash) if err == nil { - t.Fatalf("expected block %s to be pruned", blockHash) + return fmt.Errorf("expected block %s to be pruned", blockHash.String()) } if len(bytes) != 0 { - t.Fatalf("expected block %s to be pruned but got %v", + return fmt.Errorf("expected block %s to be pruned but got %v", blockHash, bytes) } + + return nil } // The below code checks that the correct blocks were pruned. - chain.db.View(func(dbTx database.Tx) error { + err = chain.db.View(func(dbTx database.Tx) error { exist := false for _, block := range blocks { // Blocks up to the last flush hash should not exist. @@ -789,15 +793,23 @@ func TestFlushOnPrune(t *testing.T) { } if exist { - shouldExist(dbTx, block.Hash()) + err = shouldExist(dbTx, block.Hash()) + if err != nil { + return err + } } else { - shouldNotExist(dbTx, block.Hash()) + err = shouldNotExist(dbTx, block.Hash()) + if err != nil { + return err + } } - } return nil }) + if err != nil { + t.Fatal(err) + } } func TestInitConsistentState(t *testing.T) { From a0c9e3b384ec6c2e54f91f00da5dccfea971504a Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Fri, 16 Feb 2024 15:49:11 +0900 Subject: [PATCH 02/33] blockchain: add case for pruning stale blocks Pruning stale blocks will make the block validation fail for the block that the prune was triggered on as BlockHeightByHash will not return the height for blocks that are not in the main chain. We add a test case to ensure that the test fails in the above case. --- blockchain/utxocache_test.go | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index 64d517b09a..2e055ea5db 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -729,18 +729,33 @@ func TestFlushOnPrune(t *testing.T) { } syncBlocks := func() { + // Modify block 1 to be a different hash. This is to artificially + // create a stale branch in the chain. + staleMsgBlock := blocks[1].MsgBlock().Copy() + staleMsgBlock.Header.Nonce = 0 + staleBlock := btcutil.NewBlock(staleMsgBlock) + + // Add the stale block here to create a chain view like so. The + // block will be the main chain at first but become stale as we + // keep adding blocks. BFNoPoWCheck is given as the pow check will + // fail. + // + // (genesis block) -> 1 -> 2 -> 3 -> ... + // \-> 1a + _, _, err = chain.ProcessBlock(staleBlock, BFNoPoWCheck) + if err != nil { + t.Fatal(err) + } + for i, block := range blocks { if i == 0 { // Skip the genesis block. continue } - isMainChain, _, err := chain.ProcessBlock(block, BFNone) + _, _, err = chain.ProcessBlock(block, BFNone) if err != nil { - t.Fatal(err) - } - - if !isMainChain { - t.Fatalf("expected block %s to be on the main chain", block.Hash()) + t.Fatalf("Failed to process block %v(%v). %v", + block.Hash().String(), block.Height(), err) } } } From 5a91ea23ca9a702310eb2ef788928c7f6d8ccba5 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 21 Feb 2024 18:14:26 +0900 Subject: [PATCH 03/33] blockchain_test, fullblocktests: add test to check for utxo existance/non-existance New test instance BlockDisconnectExpectUTXO tests that a utxo exists/doesn't exist after a specific block has been disconnected. --- blockchain/fullblocks_test.go | 66 +++++++++++++++++++++++ blockchain/fullblocktests/generate.go | 77 ++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go index d6bcf799af..591414d1d0 100644 --- a/blockchain/fullblocks_test.go +++ b/blockchain/fullblocks_test.go @@ -146,6 +146,70 @@ func TestFullBlocks(t *testing.T) { } defer teardownFunc() + testBlockDisconnectExpectUTXO := func(item fullblocktests.BlockDisconnectExpectUTXO) { + expectedCallBack := func(notification *blockchain.Notification) { + switch notification.Type { + + case blockchain.NTBlockDisconnected: + block, ok := notification.Data.(*btcutil.Block) + if !ok { + t.Fatalf("expected a block") + } + + // Return early if the block we get isn't the relevant + // block. + if !block.Hash().IsEqual(&item.BlockHash) { + return + } + + entry, err := chain.FetchUtxoEntry(item.OutPoint) + if err != nil { + t.Fatal(err) + } + + if entry == nil || entry.IsSpent() { + t.Logf("expected utxo %v to exist but it's "+ + "nil or spent\n", item.OutPoint.String()) + t.Fatalf("expected utxo %v to exist but it's "+ + "nil or spent", item.OutPoint.String()) + } + } + } + unexpectedCallBack := func(notification *blockchain.Notification) { + switch notification.Type { + case blockchain.NTBlockDisconnected: + block, ok := notification.Data.(*btcutil.Block) + if !ok { + t.Fatalf("expected a block") + } + + // Return early if the block we get isn't the relevant + // block. + if !block.Hash().IsEqual(&item.BlockHash) { + return + } + + entry, err := chain.FetchUtxoEntry(item.OutPoint) + if err != nil { + t.Fatal(err) + } + + if entry != nil && !entry.IsSpent() { + t.Logf("unexpected utxo %v to exist but it's "+ + "not nil and not spent", item.OutPoint.String()) + t.Fatalf("unexpected utxo %v exists but it's "+ + "not nil and not spent\n", item.OutPoint.String()) + } + } + } + + if item.Expected { + chain.Subscribe(expectedCallBack) + } else { + chain.Subscribe(unexpectedCallBack) + } + } + // testAcceptedBlock attempts to process the block in the provided test // instance and ensures that it was accepted according to the flags // specified in the test. @@ -300,6 +364,8 @@ func TestFullBlocks(t *testing.T) { testOrphanOrRejectedBlock(item) case fullblocktests.ExpectedTip: testExpectedTip(item) + case fullblocktests.BlockDisconnectExpectUTXO: + testBlockDisconnectExpectUTXO(item) default: t.Fatalf("test #%d, item #%d is not one of "+ "the supported test instance types -- "+ diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index 4c551c05e0..15b92540b5 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -150,6 +150,21 @@ type RejectedNonCanonicalBlock struct { // This implements the TestInstance interface. func (b RejectedNonCanonicalBlock) FullBlockTestInstance() {} +// BlockDisconnectExpectUTXO defines a test instance that tests an utxo to exist or not +// exist after a specified block has been disconnected. +type BlockDisconnectExpectUTXO struct { + Name string + Expected bool + BlockHash chainhash.Hash + OutPoint wire.OutPoint +} + +// FullBlockTestInstance only exists to allow BlockDisconnectExpectUTXO to be treated as +// a TestInstance. +// +// This implements the TestInstance interface. +func (b BlockDisconnectExpectUTXO) FullBlockTestInstance() {} + // spendableOut represents a transaction output that is spendable along with // additional metadata such as the block its in and how much it pays. type spendableOut struct { @@ -878,6 +893,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // // orphanedOrRejected creates and appends a single orphanOrRejectBlock // test instance for the current tip. + // + // blockDisconnectExpectUTXO creates and appends a BlockDisconnectExpectUTXO test + // instance with the passed in values. accepted := func() { tests = append(tests, []TestInstance{ acceptBlock(g.tipName, g.tip, true, false), @@ -904,6 +922,12 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { orphanOrRejectBlock(g.tipName, g.tip), }) } + blockDisconnectExpectUTXO := func(name string, expected bool, op wire.OutPoint, + hash chainhash.Hash) { + tests = append(tests, []TestInstance{ + BlockDisconnectExpectUTXO{name, expected, hash, op}, + }) + } // --------------------------------------------------------------------- // Generate enough blocks to have mature coinbase outputs to work with. @@ -2044,6 +2068,55 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { } accepted() + // Create a chain where the utxo created in b82a is spent in b83a. + // + // b81() -> b82a(28) -> b83a(b82.tx[1].out[0]) + // + g.nextBlock("b82a", outs[28]) + accepted() + + b82aTx1Out0 := makeSpendableOut(g.tip, 1, 0) + g.nextBlock("b83a", &b82aTx1Out0) + accepted() + + // Now we'll build a side-chain where we don't spend any of the outputs. + // + // b81() -> b82a(28) -> b83a(b82.tx[1].out[0]) + // \-> b82() -> b83() + // + g.setTip("b81") + g.nextBlock("b82", nil) + acceptedToSideChainWithExpectedTip("b83a") + + g.nextBlock("b83", nil) + acceptedToSideChainWithExpectedTip("b83a") + + // At this point b83a is still the tip. When we add block 84, the tip + // will change. Pre-load up the expected utxos test before the reorganization. + // + // We expect b82a output to now be a utxo since b83a was spending it and it was + // removed from the main chain. + blockDisconnectExpectUTXO("b82aTx1Out0", + true, b82aTx1Out0.prevOut, g.blocksByName["b83a"].BlockHash()) + + // We expect the output from b82 to not exist once b82a itself has been removed + // from the main chain. + blockDisconnectExpectUTXO("b82aTx1Out0", + false, b82aTx1Out0.prevOut, g.blocksByName["b82a"].BlockHash()) + + // The output that was being spent in b82a should exist after the removal of + // b82a. + blockDisconnectExpectUTXO("outs[28]", + true, outs[28].prevOut, g.blocksByName["b82a"].BlockHash()) + + // Create block 84 and reorg out the sidechain with b83a as the tip. + // + // b81() -> b82a(28) -> b83a(b82.tx[1].out[0]) + // \-> b82() -> b83() -> b84() + // + g.nextBlock("b84", nil) + accepted() + // --------------------------------------------------------------------- // Large block re-org test. // --------------------------------------------------------------------- @@ -2054,8 +2127,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // Ensure the tip the re-org test builds on is the best chain tip. // - // ... -> b81(27) -> ... - g.setTip("b81") + // ... -> b84() -> ... + g.setTip("b84") // Collect all of the spendable coinbase outputs from the previous // collection point up to the current tip. From 3d1150a1a853ea57059ca1ad41c558d5343a31bd Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Tue, 27 Feb 2024 14:32:42 +0900 Subject: [PATCH 04/33] blockchain: always relock chainLock for subscription callbacks For various b.sendNotifcation() callbacks, if a runtime panic happens, we don't get any useful debugging information since the error that happens first is the "unlock of unlocked mutex" error. This is because we temporarily unlock the chainLock for callbacks and then relock them. However, since the relocking code is executed after the completion of the callback, if an error happens during that callback, we never relock the chainLock. Switching to an anonymous function and having the unlock code as a defer will ensure that the lock always relocks. --- blockchain/accept.go | 8 +++++--- blockchain/chain.go | 16 ++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/blockchain/accept.go b/blockchain/accept.go index 935963148f..4adc2f6127 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -84,9 +84,11 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // Notify the caller that the new block was accepted into the block // chain. The caller would typically want to react by relaying the // inventory to other peers. - b.chainLock.Unlock() - b.sendNotification(NTBlockAccepted, block) - b.chainLock.Lock() + func() { + b.chainLock.Unlock() + defer b.chainLock.Lock() + b.sendNotification(NTBlockAccepted, block) + }() return isMainChain, nil } diff --git a/blockchain/chain.go b/blockchain/chain.go index 60420022ac..416ed92931 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -730,9 +730,11 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, // Notify the caller that the block was connected to the main chain. // The caller would typically want to react with actions such as // updating wallets. - b.chainLock.Unlock() - b.sendNotification(NTBlockConnected, block) - b.chainLock.Lock() + func() { + b.chainLock.Unlock() + defer b.chainLock.Lock() + b.sendNotification(NTBlockConnected, block) + }() // Since we may have changed the UTXO cache, we make sure it didn't exceed its // maximum size. If we're pruned and have flushed already, this will be a no-op. @@ -853,9 +855,11 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view // Notify the caller that the block was disconnected from the main // chain. The caller would typically want to react with actions such as // updating wallets. - b.chainLock.Unlock() - b.sendNotification(NTBlockDisconnected, block) - b.chainLock.Lock() + func() { + b.chainLock.Unlock() + defer b.chainLock.Lock() + b.sendNotification(NTBlockDisconnected, block) + }() return nil } From dd31767617daa87effd0fc784c7d92c824d4c178 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 14:44:24 +0800 Subject: [PATCH 05/33] btcjson: add new command `GetTxSpendingPrevOutCmd` --- btcjson/chainsvrcmds.go | 33 +++++++++++++++++++++++++++++++++ btcjson/chainsvrcmds_test.go | 24 ++++++++++++++++++++++++ btcjson/chainsvrresults.go | 14 ++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 58fa8cd1b6..956b4db604 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -1064,6 +1064,38 @@ func NewTestMempoolAcceptCmd(rawTxns []string, } } +// GetTxSpendingPrevOutCmd defines the gettxspendingprevout JSON-RPC command. +type GetTxSpendingPrevOutCmd struct { + // Outputs is a list of transaction outputs to query. + Outputs []*GetTxSpendingPrevOutCmdOutput +} + +// GetTxSpendingPrevOutCmdOutput defines the output to query for the +// gettxspendingprevout JSON-RPC command. +type GetTxSpendingPrevOutCmdOutput struct { + Txid string `json:"txid"` + Vout uint32 `json:"vout"` +} + +// NewGetTxSpendingPrevOutCmd returns a new instance which can be used to issue +// a gettxspendingprevout JSON-RPC command. +func NewGetTxSpendingPrevOutCmd( + outpoints []wire.OutPoint) *GetTxSpendingPrevOutCmd { + + outputs := make([]*GetTxSpendingPrevOutCmdOutput, 0, len(outpoints)) + + for _, op := range outpoints { + outputs = append(outputs, &GetTxSpendingPrevOutCmdOutput{ + Txid: op.Hash.String(), + Vout: op.Index, + }) + } + + return &GetTxSpendingPrevOutCmd{ + Outputs: outputs, + } +} + func init() { // No special flags for commands in this file. flags := UsageFlag(0) @@ -1125,4 +1157,5 @@ func init() { MustRegisterCmd("verifymessage", (*VerifyMessageCmd)(nil), flags) MustRegisterCmd("verifytxoutproof", (*VerifyTxOutProofCmd)(nil), flags) MustRegisterCmd("testmempoolaccept", (*TestMempoolAcceptCmd)(nil), flags) + MustRegisterCmd("gettxspendingprevout", (*GetTxSpendingPrevOutCmd)(nil), flags) } diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index eddfb03788..d3143a528c 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -1500,6 +1501,29 @@ func TestChainSvrCmds(t *testing.T) { MaxFeeRate: 0.01, }, }, + { + name: "gettxspendingprevout", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd( + "gettxspendingprevout", + []*btcjson.GetTxSpendingPrevOutCmdOutput{ + {Txid: "0000000000000000000000000000000000000000000000000000000000000001", Vout: 0}, + }) + }, + staticCmd: func() interface{} { + outputs := []wire.OutPoint{ + {Hash: chainhash.Hash{1}, Index: 0}, + } + return btcjson.NewGetTxSpendingPrevOutCmd(outputs) + }, + marshalled: `{"jsonrpc":"1.0","method":"gettxspendingprevout","params":[[{"txid":"0000000000000000000000000000000000000000000000000000000000000001","vout":0}]],"id":1}`, + unmarshalled: &btcjson.GetTxSpendingPrevOutCmd{ + Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{{ + Txid: "0000000000000000000000000000000000000000000000000000000000000001", + Vout: 0, + }}, + }, + }, } t.Logf("Running %d tests", len(tests)) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 8f59f77676..11c0483d31 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -911,3 +911,17 @@ type TestMempoolAcceptFees struct { // NOTE: this field only exists in bitcoind v25.0 and above. EffectiveIncludes []string `json:"effective-includes"` } + +// GetTxSpendingPrevOutResult defines a single item returned from the +// gettxspendingprevout command. +type GetTxSpendingPrevOutResult struct { + // Txid is the transaction id of the checked output. + Txid string `json:"txid"` + + // Vout is the vout value of the checked output. + Vout uint32 `json:"vout"` + + // SpendingTxid is the transaction id of the mempool transaction + // spending this output (omitted if unspent). + SpendingTxid string `json:"spendingtxid,omitempty"` +} From 72bbdd55a6e40e0723b56f8e50440fd49f461ead Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 15:29:57 +0800 Subject: [PATCH 06/33] rpcserver+mempool: implement `gettxspendingprevout` for `btcd` This commit adds the RPC method `gettxspendingprevout` for btcd. --- mempool/mocks.go | 4 +++ rpcserver.go | 44 +++++++++++++++++++++++++ rpcserver_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++ rpcserverhelp.go | 12 +++++++ 4 files changed, 144 insertions(+) diff --git a/mempool/mocks.go b/mempool/mocks.go index 5f50bb0730..e81309c51a 100644 --- a/mempool/mocks.go +++ b/mempool/mocks.go @@ -117,5 +117,9 @@ func (m *MockTxMempool) CheckMempoolAcceptance( func (m *MockTxMempool) CheckSpend(op wire.OutPoint) *btcutil.Tx { args := m.Called(op) + if args.Get(0) == nil { + return nil + } + return args.Get(0).(*btcutil.Tx) } diff --git a/rpcserver.go b/rpcserver.go index 2433286ac7..d6f3167f1e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -185,6 +185,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "verifymessage": handleVerifyMessage, "version": handleVersion, "testmempoolaccept": handleTestMempoolAccept, + "gettxspendingprevout": handleGetTxSpendingPrevOut, } // list of commands that we recognize, but for which btcd has no support because @@ -3906,6 +3907,49 @@ func handleTestMempoolAccept(s *rpcServer, cmd interface{}, return results, nil } +// handleGetTxSpendingPrevOut implements the gettxspendingprevout command. +func handleGetTxSpendingPrevOut(s *rpcServer, cmd interface{}, + closeChan <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetTxSpendingPrevOutCmd) + + // Convert the outpoints. + ops := make([]wire.OutPoint, 0, len(c.Outputs)) + for _, o := range c.Outputs { + hash, err := chainhash.NewHashFromStr(o.Txid) + if err != nil { + return nil, err + } + + ops = append(ops, wire.OutPoint{ + Hash: *hash, + Index: o.Vout, + }) + } + + // Check mempool spend for all the outpoints. + results := make([]*btcjson.GetTxSpendingPrevOutResult, 0, len(ops)) + for _, op := range ops { + // Create a result entry. + result := &btcjson.GetTxSpendingPrevOutResult{ + Txid: op.Hash.String(), + Vout: op.Index, + } + + // Check the mempool spend. + spendingTx := s.cfg.TxMemPool.CheckSpend(op) + + // Set the spending txid if found. + if spendingTx != nil { + result.SpendingTxid = spendingTx.Hash().String() + } + + results = append(results, result) + } + + return results, nil +} + // validateFeeRate checks that the fee rate used by transaction doesn't exceed // the max fee rate specified. func validateFeeRate(feeSats btcutil.Amount, txSize int64, diff --git a/rpcserver_test.go b/rpcserver_test.go index 6ca15766c3..0aa9391321 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mempool" + "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) @@ -411,3 +412,86 @@ func TestHandleTestMempoolAcceptFees(t *testing.T) { }) } } + +// TestGetTxSpendingPrevOut checks that handleGetTxSpendingPrevOut handles the +// cmd as expected. +func TestGetTxSpendingPrevOut(t *testing.T) { + t.Parallel() + + require := require.New(t) + + // Create a mock mempool. + mm := &mempool.MockTxMempool{} + defer mm.AssertExpectations(t) + + // Create a testing server with the mock mempool. + s := &rpcServer{cfg: rpcserverConfig{ + TxMemPool: mm, + }} + + // First, check the error case. + // + // Create a request that will cause an error. + cmd := &btcjson.GetTxSpendingPrevOutCmd{ + Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{ + {Txid: "invalid"}, + }, + } + + // Call the method handler and assert the error is returned. + closeChan := make(chan struct{}) + results, err := handleGetTxSpendingPrevOut(s, cmd, closeChan) + require.Error(err) + require.Nil(results) + + // We now check the normal case. Two outputs will be tested - one found + // in mempool and other not. + // + // Decode the hex so we can assert the mock mempool is called with it. + tx := decodeTxHex(t, txHex1) + + // Create testing outpoints. + opInMempool := wire.OutPoint{Hash: chainhash.Hash{1}, Index: 1} + opNotInMempool := wire.OutPoint{Hash: chainhash.Hash{2}, Index: 1} + + // We only expect to see one output being found as spent in mempool. + expectedResults := []*btcjson.GetTxSpendingPrevOutResult{ + { + Txid: opInMempool.Hash.String(), + Vout: opInMempool.Index, + SpendingTxid: tx.Hash().String(), + }, + { + Txid: opNotInMempool.Hash.String(), + Vout: opNotInMempool.Index, + }, + } + + // We mock the first call to `CheckSpend` to return a result saying the + // output is found. + mm.On("CheckSpend", opInMempool).Return(tx).Once() + + // We mock the second call to `CheckSpend` to return a result saying the + // output is NOT found. + mm.On("CheckSpend", opNotInMempool).Return(nil).Once() + + // Create a request with the above outputs. + cmd = &btcjson.GetTxSpendingPrevOutCmd{ + Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{ + { + Txid: opInMempool.Hash.String(), + Vout: opInMempool.Index, + }, + { + Txid: opNotInMempool.Hash.String(), + Vout: opNotInMempool.Index, + }, + }, + } + + // Call the method handler and assert the expected result is returned. + closeChan = make(chan struct{}) + results, err = handleGetTxSpendingPrevOut(s, cmd, closeChan) + require.NoError(err) + require.Equal(expectedResults, results) +} diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 0ee8485180..0cc384db2c 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -734,6 +734,17 @@ var helpDescsEnUS = map[string]string{ "testmempoolacceptfees-base": "Transaction fees (only present if 'allowed' is true).", "testmempoolacceptfees-effective-feerate": "The effective feerate in BTC per KvB.", "testmempoolacceptfees-effective-includes": "Transactions whose fees and vsizes are included in effective-feerate. Each item is a transaction wtxid in hex.", + + // GetTxSpendingPrevOutCmd help. + "gettxspendingprevout--synopsis": "Scans the mempool to find transactions spending any of the given outputs", + "gettxspendingprevout-outputs": "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).", + "gettxspendingprevout-txid": "The transaction id", + "gettxspendingprevout-vout": "The output number", + + // GetTxSpendingPrevOutCmd result help. + "gettxspendingprevoutresult-txid": "The transaction hash in hex.", + "gettxspendingprevoutresult-vout": "The output index.", + "gettxspendingprevoutresult-spendingtxid": "The hash of the transaction that spends the output.", } // rpcResultTypes specifies the result types that each RPC command can return. @@ -790,6 +801,7 @@ var rpcResultTypes = map[string][]interface{}{ "verifymessage": {(*bool)(nil)}, "version": {(*map[string]btcjson.VersionResult)(nil)}, "testmempoolaccept": {(*[]btcjson.TestMempoolAcceptResult)(nil)}, + "gettxspendingprevout": {(*[]btcjson.GetTxSpendingPrevOutResult)(nil)}, // Websocket commands. "loadtxfilter": nil, From 5f71df165e53d8bb0b9a1f7f0b1c8c4c0e7a4781 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 16:43:04 +0800 Subject: [PATCH 07/33] rpcclient: track bitcoind version 24.0.0 We need this for `gettxspendingprevout`. --- rpcclient/infrastructure.go | 17 +++++++++++++++-- rpcclient/infrastructure_test.go | 11 ++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 8543106b15..ad2b391a22 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -114,8 +114,12 @@ const ( // 0.19.0 and smaller than 22.0.0. BitcoindPre22 + // BitcoindPre24 represents a bitcoind version equal to or greater than + // 22.0.0 and smaller than 24.0.0. + BitcoindPre24 + // BitcoindPre25 represents a bitcoind version equal to or greater than - // 22.0.0 and smaller than 25.0.0. + // 24.0.0 and smaller than 25.0.0. BitcoindPre25 // BitcoindPre25 represents a bitcoind version equal to or greater than @@ -135,8 +139,11 @@ func (b BackendVersion) String() string { case BitcoindPre22: return "bitcoind v0.19.0-v22.0.0" + case BitcoindPre24: + return "bitcoind v22.0.0-v24.0.0" + case BitcoindPre25: - return "bitcoind v22.0.0-v25.0.0" + return "bitcoind v24.0.0-v25.0.0" case BitcoindPost25: return "bitcoind v25.0.0 and above" @@ -1625,6 +1632,9 @@ const ( // bitcoind22Str is the string representation of bitcoind v22.0.0. bitcoind22Str = "22.0.0" + // bitcoind24Str is the string representation of bitcoind v24.0.0. + bitcoind24Str = "24.0.0" + // bitcoind25Str is the string representation of bitcoind v25.0.0. bitcoind25Str = "25.0.0" @@ -1653,6 +1663,9 @@ func parseBitcoindVersion(version string) BackendVersion { case version < bitcoind22Str: return BitcoindPre22 + case version < bitcoind24Str: + return BitcoindPre24 + case version < bitcoind25Str: return BitcoindPre25 diff --git a/rpcclient/infrastructure_test.go b/rpcclient/infrastructure_test.go index e97fa275c0..bf09831a2e 100644 --- a/rpcclient/infrastructure_test.go +++ b/rpcclient/infrastructure_test.go @@ -34,11 +34,16 @@ func TestParseBitcoindVersion(t *testing.T) { { name: "parse version 22.0", rpcVersion: "/Satoshi:22.0.0/", - parsedVersion: BitcoindPre25, + parsedVersion: BitcoindPre24, + }, + { + name: "parse version 22.0 - 24.0", + rpcVersion: "/Satoshi:23.1.0/", + parsedVersion: BitcoindPre24, }, { - name: "parse version 22.0 - 25.0", - rpcVersion: "/Satoshi:23.0.0/", + name: "parse version 24.0", + rpcVersion: "/Satoshi:24.0.0/", parsedVersion: BitcoindPre25, }, { From a033b0d6e70353d3a6f33b3bdeca08088b9dd22a Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 16:30:14 +0800 Subject: [PATCH 08/33] rpcclient+integration: add new method `GetTxSpendingPrevOut` --- integration/chain_test.go | 146 +++++++++++++++++++++++++++++++++++ rpcclient/rawtransactions.go | 68 ++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 integration/chain_test.go diff --git a/integration/chain_test.go b/integration/chain_test.go new file mode 100644 index 0000000000..0f5cd94c83 --- /dev/null +++ b/integration/chain_test.go @@ -0,0 +1,146 @@ +//go:build rpctest +// +build rpctest + +package integration + +import ( + "testing" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +// TestGetTxSpendingPrevOut checks that `GetTxSpendingPrevOut` behaves as +// expected. +// - an error is returned when invalid params are used. +// - orphan tx is rejected. +// - fee rate above the max is rejected. +// - a mixed of both allowed and rejected can be returned in the same response. +func TestGetTxSpendingPrevOut(t *testing.T) { + t.Parallel() + + // Boilerplate codetestDir to make a pruned node. + btcdCfg := []string{"--rejectnonstd", "--debuglevel=debug"} + r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg, "") + require.NoError(t, err) + + // Setup the node. + require.NoError(t, r.SetUp(true, 100)) + t.Cleanup(func() { + require.NoError(t, r.TearDown()) + }) + + // Create a tx and testing outpoints. + tx := createTxInMempool(t, r) + opInMempool := tx.TxIn[0].PreviousOutPoint + opNotInMempool := wire.OutPoint{ + Hash: tx.TxHash(), + Index: 0, + } + + testCases := []struct { + name string + outpoints []wire.OutPoint + expectedErr error + expectedResult []*btcjson.GetTxSpendingPrevOutResult + }{ + { + // When no outpoints are provided, the method should + // return an error. + name: "empty outpoints", + expectedErr: rpcclient.ErrInvalidParam, + expectedResult: nil, + }, + { + // When there are outpoints provided, check the + // expceted results are returned. + name: "outpoints", + outpoints: []wire.OutPoint{ + opInMempool, opNotInMempool, + }, + expectedErr: nil, + expectedResult: []*btcjson.GetTxSpendingPrevOutResult{ + { + Txid: opInMempool.Hash.String(), + Vout: opInMempool.Index, + SpendingTxid: tx.TxHash().String(), + }, + { + Txid: opNotInMempool.Hash.String(), + Vout: opNotInMempool.Index, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + + results, err := r.Client.GetTxSpendingPrevOut( + tc.outpoints, + ) + + require.ErrorIs(err, tc.expectedErr) + require.Len(results, len(tc.expectedResult)) + + // Check each item is returned as expected. + for i, r := range results { + e := tc.expectedResult[i] + + require.Equal(e.Txid, r.Txid) + require.Equal(e.Vout, r.Vout) + require.Equal(e.SpendingTxid, r.SpendingTxid) + } + }) + } +} + +// createTxInMempool creates a tx and puts it in the mempool. +func createTxInMempool(t *testing.T, r *rpctest.Harness) *wire.MsgTx { + // Create a fresh output for usage within the test below. + const outputValue = btcutil.SatoshiPerBitcoin + outputKey, testOutput, testPkScript, err := makeTestOutput( + r, t, outputValue, + ) + require.NoError(t, err) + + // Create a new transaction with a lock-time past the current known + // MTP. + tx := wire.NewMsgTx(1) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *testOutput, + }) + + // Fetch a fresh address from the harness, we'll use this address to + // send funds back into the Harness. + addr, err := r.NewAddress() + require.NoError(t, err) + + addrScript, err := txscript.PayToAddrScript(addr) + require.NoError(t, err) + + tx.AddTxOut(&wire.TxOut{ + PkScript: addrScript, + Value: outputValue - 1000, + }) + + sigScript, err := txscript.SignatureScript( + tx, 0, testPkScript, txscript.SigHashAll, outputKey, true, + ) + require.NoError(t, err) + tx.TxIn[0].SignatureScript = sigScript + + // Send the tx. + _, err = r.Client.SendRawTransaction(tx, true) + require.NoError(t, err) + + return tx +} diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index a683021946..4737aee5b9 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -1012,3 +1012,71 @@ func (c *Client) TestMempoolAccept(txns []*wire.MsgTx, return c.TestMempoolAcceptAsync(txns, maxFeeRate).Receive() } + +// FutureGetTxSpendingPrevOut is a future promise to deliver the result of a +// GetTxSpendingPrevOut RPC invocation (or an applicable error). +type FutureGetTxSpendingPrevOut chan *Response + +// Receive waits for the Response promised by the future and returns the +// response from GetTxSpendingPrevOut. +func (r FutureGetTxSpendingPrevOut) Receive() ( + []*btcjson.GetTxSpendingPrevOutResult, error) { + + response, err := ReceiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal as an array of GetTxSpendingPrevOutResult items. + var results []*btcjson.GetTxSpendingPrevOutResult + + err = json.Unmarshal(response, &results) + if err != nil { + return nil, err + } + + return results, nil +} + +// GetTxSpendingPrevOutAsync 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 GetTxSpendingPrevOut for the blocking version and more details. +func (c *Client) GetTxSpendingPrevOutAsync( + outpoints []wire.OutPoint) FutureGetTxSpendingPrevOut { + + // Due to differences in the testmempoolaccept 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) + } + + log.Debugf("GetTxSpendingPrevOutAsync: backend version %s", version) + + // Exit early if the version is below 24.0.0. + if version < BitcoindPre24 { + err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version) + return newFutureError(err) + } + + // Exit early if an empty array of outpoints is provided. + if len(outpoints) == 0 { + err := fmt.Errorf("%w: no outpoints provided", ErrInvalidParam) + return newFutureError(err) + } + + cmd := btcjson.NewGetTxSpendingPrevOutCmd(outpoints) + + return c.SendCmd(cmd) +} + +// GetTxSpendingPrevOut returns the result from calling `gettxspendingprevout` +// RPC. +func (c *Client) GetTxSpendingPrevOut(outpoints []wire.OutPoint) ( + []*btcjson.GetTxSpendingPrevOutResult, error) { + + return c.GetTxSpendingPrevOutAsync(outpoints).Receive() +} From 0d52a890f26c3aef21a38f7a8ad349a22aa5984a Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 18:51:34 +0800 Subject: [PATCH 09/33] rpcclient: handle backend versioning in one file --- rpcclient/backend_version.go | 106 ++++++++++++++++++ ...ucture_test.go => backend_version_test.go} | 0 rpcclient/infrastructure.go | 104 ----------------- 3 files changed, 106 insertions(+), 104 deletions(-) create mode 100644 rpcclient/backend_version.go rename rpcclient/{infrastructure_test.go => backend_version_test.go} (100%) diff --git a/rpcclient/backend_version.go b/rpcclient/backend_version.go new file mode 100644 index 0000000000..70037e150b --- /dev/null +++ b/rpcclient/backend_version.go @@ -0,0 +1,106 @@ +package rpcclient + +import "strings" + +// 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 + + // BitcoindPre22 represents a bitcoind version equal to or greater than + // 0.19.0 and smaller than 22.0.0. + BitcoindPre22 + + // BitcoindPre24 represents a bitcoind version equal to or greater than + // 22.0.0 and smaller than 24.0.0. + BitcoindPre24 + + // BitcoindPre25 represents a bitcoind version equal to or greater than + // 24.0.0 and smaller than 25.0.0. + BitcoindPre25 + + // BitcoindPre25 represents a bitcoind version equal to or greater than + // 25.0.0. + BitcoindPost25 + + // Btcd represents a catch-all btcd version. + Btcd +) + +// String returns a human-readable backend version. +func (b BackendVersion) String() string { + switch b { + case BitcoindPre19: + return "bitcoind 0.19 and below" + + case BitcoindPre22: + return "bitcoind v0.19.0-v22.0.0" + + case BitcoindPre24: + return "bitcoind v22.0.0-v24.0.0" + + case BitcoindPre25: + return "bitcoind v24.0.0-v25.0.0" + + case BitcoindPost25: + return "bitcoind v25.0.0 and above" + + case Btcd: + return "btcd" + + default: + return "unknown" + } +} + +const ( + // bitcoind19Str is the string representation of bitcoind v0.19.0. + bitcoind19Str = "0.19.0" + + // bitcoind22Str is the string representation of bitcoind v22.0.0. + bitcoind22Str = "22.0.0" + + // bitcoind24Str is the string representation of bitcoind v24.0.0. + bitcoind24Str = "24.0.0" + + // bitcoind25Str is the string representation of bitcoind v25.0.0. + bitcoind25Str = "25.0.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 + + case version < bitcoind22Str: + return BitcoindPre22 + + case version < bitcoind24Str: + return BitcoindPre24 + + case version < bitcoind25Str: + return BitcoindPre25 + + default: + return BitcoindPost25 + } +} diff --git a/rpcclient/infrastructure_test.go b/rpcclient/backend_version_test.go similarity index 100% rename from rpcclient/infrastructure_test.go rename to rpcclient/backend_version_test.go diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index ad2b391a22..404a7b4a04 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -20,7 +20,6 @@ import ( "net/http" "net/url" "os" - "strings" "sync" "sync/atomic" "time" @@ -102,60 +101,6 @@ 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 - - // BitcoindPre22 represents a bitcoind version equal to or greater than - // 0.19.0 and smaller than 22.0.0. - BitcoindPre22 - - // BitcoindPre24 represents a bitcoind version equal to or greater than - // 22.0.0 and smaller than 24.0.0. - BitcoindPre24 - - // BitcoindPre25 represents a bitcoind version equal to or greater than - // 24.0.0 and smaller than 25.0.0. - BitcoindPre25 - - // BitcoindPre25 represents a bitcoind version equal to or greater than - // 25.0.0. - BitcoindPost25 - - // Btcd represents a catch-all btcd version. - Btcd -) - -// String returns a human-readable backend version. -func (b BackendVersion) String() string { - switch b { - case BitcoindPre19: - return "bitcoind 0.19 and below" - - case BitcoindPre22: - return "bitcoind v0.19.0-v22.0.0" - - case BitcoindPre24: - return "bitcoind v22.0.0-v24.0.0" - - case BitcoindPre25: - return "bitcoind v24.0.0-v25.0.0" - - case BitcoindPost25: - return "bitcoind v25.0.0 and above" - - case Btcd: - return "btcd" - - default: - return "unknown" - } -} - // 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 @@ -1625,55 +1570,6 @@ func (c *Client) Connect(tries int) error { return err } -const ( - // bitcoind19Str is the string representation of bitcoind v0.19.0. - bitcoind19Str = "0.19.0" - - // bitcoind22Str is the string representation of bitcoind v22.0.0. - bitcoind22Str = "22.0.0" - - // bitcoind24Str is the string representation of bitcoind v24.0.0. - bitcoind24Str = "24.0.0" - - // bitcoind25Str is the string representation of bitcoind v25.0.0. - bitcoind25Str = "25.0.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 - - case version < bitcoind22Str: - return BitcoindPre22 - - case version < bitcoind24Str: - return BitcoindPre24 - - case version < bitcoind25Str: - return BitcoindPre25 - - default: - return BitcoindPost25 - } -} - // BackendVersion retrieves the version of the backend the client is currently // connected to. func (c *Client) BackendVersion() (BackendVersion, error) { From 9e6070a70713c63877353685e183a9e7bf575895 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 21:57:33 +0800 Subject: [PATCH 10/33] rpcclient: start tracking `btcd` version --- rpcclient/backend_version.go | 50 +++++++++++++++++++++++++++---- rpcclient/backend_version_test.go | 37 +++++++++++++++++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/rpcclient/backend_version.go b/rpcclient/backend_version.go index 70037e150b..e2d56b0dc6 100644 --- a/rpcclient/backend_version.go +++ b/rpcclient/backend_version.go @@ -25,9 +25,6 @@ const ( // BitcoindPre25 represents a bitcoind version equal to or greater than // 25.0.0. BitcoindPost25 - - // Btcd represents a catch-all btcd version. - Btcd ) // String returns a human-readable backend version. @@ -48,9 +45,6 @@ func (b BackendVersion) String() string { case BitcoindPost25: return "bitcoind v25.0.0 and above" - case Btcd: - return "btcd" - default: return "unknown" } @@ -104,3 +98,47 @@ func parseBitcoindVersion(version string) BackendVersion { return BitcoindPost25 } } + +// BtcdVersion represents the version of the btcd the client is currently +// connected to. +type BtcdVersion int32 + +const ( + // BtcdPre2401 describes a btcd version before 0.24.1, which doesn't + // include the `testmempoolaccept` and `gettxspendingprevout` RPCs. + BtcdPre2401 BtcdVersion = iota + + // BtcdPost2401 describes a btcd version equal to or greater than + // 0.24.1. + BtcdPost2401 +) + +// String returns a human-readable backend version. +func (b BtcdVersion) String() string { + switch b { + case BtcdPre2401: + return "btcd 24.0.0 and below" + + case BtcdPost2401: + return "btcd 24.1.0 and above" + + default: + return "unknown" + } +} + +const ( + // btcd2401Val is the int representation of btcd v0.24.1. + btcd2401Val = 240100 +) + +// parseBtcdVersion parses the btcd version from its string representation. +func parseBtcdVersion(version int32) BtcdVersion { + switch { + case version < btcd2401Val: + return BtcdPre2401 + + default: + return BtcdPost2401 + } +} diff --git a/rpcclient/backend_version_test.go b/rpcclient/backend_version_test.go index bf09831a2e..91c84ad4f2 100644 --- a/rpcclient/backend_version_test.go +++ b/rpcclient/backend_version_test.go @@ -67,3 +67,40 @@ func TestParseBitcoindVersion(t *testing.T) { }) } } + +// TestParseBtcdVersion checks that the correct version from btcd's `getinfo` +// RPC call is parsed. +func TestParseBtcdVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + rpcVersion int32 + parsedVersion BtcdVersion + }{ + { + name: "parse version 0.24 and below", + rpcVersion: 230000, + parsedVersion: BtcdPre2401, + }, + { + name: "parse version 0.24.1", + rpcVersion: 240100, + parsedVersion: BtcdPost2401, + }, + { + name: "parse version 0.24.1 and above", + rpcVersion: 250000, + parsedVersion: BtcdPost2401, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + version := parseBtcdVersion(tc.rpcVersion) + require.Equal(t, tc.parsedVersion, version) + }) + } +} From 4cf49bd72c7da1e095bc3c8a992678e81fc03333 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 23 Feb 2024 22:09:44 +0800 Subject: [PATCH 11/33] multi: turn `BackendVersion` into an interface This commit adds a new interface, `BackendVersion`, to support checking versions for multiple both btcd and bitcoind. Once neutrino version is also pinned here, it should also be checked. --- rpcclient/backend_version.go | 76 ++++++++++++++++++++++++++++--- rpcclient/backend_version_test.go | 44 +++++++++++++++++- rpcclient/chain.go | 2 +- rpcclient/infrastructure.go | 19 ++++---- rpcclient/rawtransactions.go | 8 ++-- 5 files changed, 129 insertions(+), 20 deletions(-) diff --git a/rpcclient/backend_version.go b/rpcclient/backend_version.go index e2d56b0dc6..cb2a46fc5e 100644 --- a/rpcclient/backend_version.go +++ b/rpcclient/backend_version.go @@ -2,13 +2,32 @@ package rpcclient import "strings" -// BackendVersion represents the version of the backend the client is currently -// connected to. -type BackendVersion uint8 +// BackendVersion defines an interface to handle the version of the backend +// used by the client. +type BackendVersion interface { + // String returns a human-readable backend version. + String() string + + // SupportUnifiedSoftForks returns true if the backend supports the + // unified softforks format. + SupportUnifiedSoftForks() bool + + // SupportTestMempoolAccept returns true if the backend supports the + // testmempoolaccept RPC. + SupportTestMempoolAccept() bool + + // SupportGetTxSpendingPrevOut returns true if the backend supports the + // gettxspendingprevout RPC. + SupportGetTxSpendingPrevOut() bool +} + +// BitcoindVersion represents the version of the bitcoind the client is +// currently connected to. +type BitcoindVersion uint8 const ( // BitcoindPre19 represents a bitcoind version before 0.19.0. - BitcoindPre19 BackendVersion = iota + BitcoindPre19 BitcoindVersion = iota // BitcoindPre22 represents a bitcoind version equal to or greater than // 0.19.0 and smaller than 22.0.0. @@ -28,7 +47,7 @@ const ( ) // String returns a human-readable backend version. -func (b BackendVersion) String() string { +func (b BitcoindVersion) String() string { switch b { case BitcoindPre19: return "bitcoind 0.19 and below" @@ -50,6 +69,29 @@ func (b BackendVersion) String() string { } } +// SupportUnifiedSoftForks returns true if the backend supports the unified +// softforks format. +func (b BitcoindVersion) SupportUnifiedSoftForks() bool { + // Versions of bitcoind on or after v0.19.0 use the unified format. + return b > BitcoindPre19 +} + +// SupportTestMempoolAccept returns true if bitcoind version is 22.0.0 or +// above. +func (b BitcoindVersion) SupportTestMempoolAccept() bool { + return b > BitcoindPre22 +} + +// SupportGetTxSpendingPrevOut returns true if bitcoind version is 24.0.0 or +// above. +func (b BitcoindVersion) SupportGetTxSpendingPrevOut() bool { + return b > BitcoindPre24 +} + +// Compile-time checks to ensure that BitcoindVersion satisfy the +// BackendVersion interface. +var _ BackendVersion = BitcoindVersion(0) + const ( // bitcoind19Str is the string representation of bitcoind v0.19.0. bitcoind19Str = "0.19.0" @@ -74,7 +116,7 @@ const ( // parseBitcoindVersion parses the bitcoind version from its string // representation. -func parseBitcoindVersion(version string) BackendVersion { +func parseBitcoindVersion(version string) BitcoindVersion { // Trim the version of its prefix and suffix to determine the // appropriate version number. version = strings.TrimPrefix( @@ -127,6 +169,28 @@ func (b BtcdVersion) String() string { } } +// SupportUnifiedSoftForks returns true if the backend supports the unified +// softforks format. +// +// NOTE: always true for btcd as we didn't track it before. +func (b BtcdVersion) SupportUnifiedSoftForks() bool { + return true +} + +// SupportTestMempoolAccept returns true if btcd version is 24.1.0 or above. +func (b BtcdVersion) SupportTestMempoolAccept() bool { + return b > BtcdPre2401 +} + +// SupportGetTxSpendingPrevOut returns true if btcd version is 24.1.0 or above. +func (b BtcdVersion) SupportGetTxSpendingPrevOut() bool { + return b > BtcdPre2401 +} + +// Compile-time checks to ensure that BtcdVersion satisfy the BackendVersion +// interface. +var _ BackendVersion = BtcdVersion(0) + const ( // btcd2401Val is the int representation of btcd v0.24.1. btcd2401Val = 240100 diff --git a/rpcclient/backend_version_test.go b/rpcclient/backend_version_test.go index 91c84ad4f2..3a4baec1db 100644 --- a/rpcclient/backend_version_test.go +++ b/rpcclient/backend_version_test.go @@ -14,7 +14,7 @@ func TestParseBitcoindVersion(t *testing.T) { testCases := []struct { name string rpcVersion string - parsedVersion BackendVersion + parsedVersion BitcoindVersion }{ { name: "parse version 0.19 and below", @@ -104,3 +104,45 @@ func TestParseBtcdVersion(t *testing.T) { }) } } + +// TestVersionSupports checks all the versions of bitcoind and btcd to ensure +// that the RPCs are supported correctly. +func TestVersionSupports(t *testing.T) { + t.Parallel() + + require := require.New(t) + + // For bitcoind, unified softforks format is supported in 19.0 and + // above. + require.False(BitcoindPre19.SupportUnifiedSoftForks()) + require.True(BitcoindPre22.SupportUnifiedSoftForks()) + require.True(BitcoindPre24.SupportUnifiedSoftForks()) + require.True(BitcoindPre25.SupportUnifiedSoftForks()) + require.True(BitcoindPost25.SupportUnifiedSoftForks()) + + // For bitcoind, `testmempoolaccept` is supported in 22.0 and above. + require.False(BitcoindPre19.SupportTestMempoolAccept()) + require.False(BitcoindPre22.SupportTestMempoolAccept()) + require.True(BitcoindPre24.SupportTestMempoolAccept()) + require.True(BitcoindPre25.SupportTestMempoolAccept()) + require.True(BitcoindPost25.SupportTestMempoolAccept()) + + // For bitcoind, `gettxspendingprevout` is supported in 24.0 and above. + require.False(BitcoindPre19.SupportGetTxSpendingPrevOut()) + require.False(BitcoindPre22.SupportGetTxSpendingPrevOut()) + require.False(BitcoindPre24.SupportGetTxSpendingPrevOut()) + require.True(BitcoindPre25.SupportGetTxSpendingPrevOut()) + require.True(BitcoindPost25.SupportGetTxSpendingPrevOut()) + + // For btcd, unified softforks format is supported in all versions. + require.True(BtcdPre2401.SupportUnifiedSoftForks()) + require.True(BtcdPost2401.SupportUnifiedSoftForks()) + + // For btcd, `testmempoolaccept` is supported in 24.1 and above. + require.False(BtcdPre2401.SupportTestMempoolAccept()) + require.True(BtcdPost2401.SupportTestMempoolAccept()) + + // For btcd, `gettxspendingprevout` is supported in 24.1 and above. + require.False(BtcdPre2401.SupportGetTxSpendingPrevOut()) + require.True(BtcdPost2401.SupportGetTxSpendingPrevOut()) +} diff --git a/rpcclient/chain.go b/rpcclient/chain.go index f2ce1ea626..e65c40b3a8 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -441,7 +441,7 @@ func unmarshalGetBlockChainInfoResultSoftForks(chainInfo *btcjson.GetBlockChainI version BackendVersion, res []byte) error { // Versions of bitcoind on or after v0.19.0 use the unified format. - if version > BitcoindPre19 { + if version.SupportUnifiedSoftForks() { var softForks btcjson.UnifiedSoftForks if err := json.Unmarshal(res, &softForks); err != nil { return err diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 404a7b4a04..39cfa3f474 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -134,7 +134,7 @@ type Client struct { // backendVersion is the version of the backend the client is currently // connected to. This should be retrieved through GetVersion. backendVersionMu sync.Mutex - backendVersion *BackendVersion + backendVersion BackendVersion // mtx is a mutex to protect access to connection related fields. mtx sync.Mutex @@ -1577,7 +1577,7 @@ func (c *Client) BackendVersion() (BackendVersion, error) { defer c.backendVersionMu.Unlock() if c.backendVersion != nil { - return *c.backendVersion, nil + return c.backendVersion, nil } // We'll start by calling GetInfo. This method doesn't exist for @@ -1589,20 +1589,20 @@ func (c *Client) BackendVersion() (BackendVersion, error) { // 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 + version := parseBtcdVersion(info.Version) + 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: "+ + return nil, fmt.Errorf("unable to detect btcd version: "+ "%v", err) } default: - return 0, fmt.Errorf("unable to detect btcd version: %v", err) + return nil, fmt.Errorf("unable to detect btcd version: %v", err) } // Since the GetInfo method was not found, we assume the client is @@ -1610,7 +1610,8 @@ func (c *Client) BackendVersion() (BackendVersion, error) { // GetNetworkInfo. networkInfo, err := c.GetNetworkInfo() if err != nil { - return 0, fmt.Errorf("unable to detect bitcoind version: %v", err) + return nil, fmt.Errorf("unable to detect bitcoind version: %v", + err) } // Parse the bitcoind version and cache it. @@ -1618,7 +1619,7 @@ func (c *Client) BackendVersion() (BackendVersion, error) { version := parseBitcoindVersion(networkInfo.SubVersion) c.backendVersion = &version - return *c.backendVersion, nil + return c.backendVersion, nil } func (c *Client) sendAsync() FutureGetBulkResult { diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 4737aee5b9..3197808f36 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -360,7 +360,9 @@ func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) Fut var cmd *btcjson.SendRawTransactionCmd // Starting from bitcoind v0.19.0, the MaxFeeRate field should be used. - if version > BitcoindPre19 { + // + // When unified softforks format is supported, it's 0.19 and above. + if version.SupportUnifiedSoftForks() { // Using a 0 MaxFeeRate is interpreted as a maximum fee rate not // being enforced by bitcoind. var maxFeeRate int32 @@ -943,7 +945,7 @@ func (c *Client) TestMempoolAcceptAsync(txns []*wire.MsgTx, // // We decide to not support this call for versions below 22.0.0. as the // request/response formats are very different. - if version < BitcoindPre22 { + if !version.SupportTestMempoolAccept() { err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version) return newFutureError(err) } @@ -1057,7 +1059,7 @@ func (c *Client) GetTxSpendingPrevOutAsync( log.Debugf("GetTxSpendingPrevOutAsync: backend version %s", version) // Exit early if the version is below 24.0.0. - if version < BitcoindPre24 { + if !version.SupportGetTxSpendingPrevOut() { err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version) return newFutureError(err) } From 34721f02c16d71d493fa7b6055f45772ee0b60c8 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 28 Feb 2024 03:38:48 +0800 Subject: [PATCH 12/33] rpcclient: replace `ErrBitcoindVersion` with `ErrBackendVersion` --- rpcclient/errors.go | 7 ++++--- rpcclient/rawtransactions.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rpcclient/errors.go b/rpcclient/errors.go index 78f34bef2e..09e1cb3c22 100644 --- a/rpcclient/errors.go +++ b/rpcclient/errors.go @@ -6,9 +6,10 @@ import ( ) var ( - // ErrBitcoindVersion is returned when running against a bitcoind that - // is older than the minimum version supported by the rpcclient. - ErrBitcoindVersion = errors.New("bitcoind version too low") + // ErrBackendVersion is returned when running against a bitcoind or + // btcd that is older than the minimum version supported by the + // rpcclient. + ErrBackendVersion = errors.New("backend version too low") // ErrInvalidParam is returned when the caller provides an invalid // parameter to an RPC method. diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 3197808f36..9c95041fd4 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -946,7 +946,7 @@ func (c *Client) TestMempoolAcceptAsync(txns []*wire.MsgTx, // We decide to not support this call for versions below 22.0.0. as the // request/response formats are very different. if !version.SupportTestMempoolAccept() { - err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version) + err := fmt.Errorf("%w: %v", ErrBackendVersion, version) return newFutureError(err) } @@ -1060,7 +1060,7 @@ func (c *Client) GetTxSpendingPrevOutAsync( // Exit early if the version is below 24.0.0. if !version.SupportGetTxSpendingPrevOut() { - err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version) + err := fmt.Errorf("%w: %v", ErrBackendVersion, version) return newFutureError(err) } From 0d4ed16ed7bcae516480d24f326d8dd3b8e8975c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 28 Feb 2024 16:40:11 +0800 Subject: [PATCH 13/33] btcd: increase version number to `0.24.1` --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index d7835910f8..999dfd95b2 100644 --- a/version.go +++ b/version.go @@ -18,7 +18,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 24 - appPatch uint = 0 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. From f9da3387f1766a5c8e30662710b48915f3549bda Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Tue, 5 Mar 2024 16:23:24 +0900 Subject: [PATCH 14/33] addrmgr: fix intermittent addrmanager_internal_test bug Sometimes the tests in the addrmanager_internal_test will fail with: "addrmanager_internal_test.go: expected to find 5 addresses, found 4" This is because the generated address with randAddr() may not always generate an address that's routable. If the address is not routable, that address will not be added to the manager when calling AddAddress(). This bug is fixed by creating a new function routableRandAddr() that always creates a routable random address. --- addrmgr/addrmanager_internal_test.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/addrmgr/addrmanager_internal_test.go b/addrmgr/addrmanager_internal_test.go index 1d13f78e6e..b58ff4b469 100644 --- a/addrmgr/addrmanager_internal_test.go +++ b/addrmgr/addrmanager_internal_test.go @@ -12,7 +12,7 @@ import ( ) // randAddr generates a *wire.NetAddressV2 backed by a random IPv4/IPv6 -// address. +// address. Some of the returned addresses may not be routable. func randAddr(t *testing.T) *wire.NetAddressV2 { t.Helper() @@ -40,6 +40,23 @@ func randAddr(t *testing.T) *wire.NetAddressV2 { ) } +// routableRandAddr generates a *wire.NetAddressV2 backed by a random IPv4/IPv6 +// address that is always routable. +func routableRandAddr(t *testing.T) *wire.NetAddressV2 { + t.Helper() + + var addr *wire.NetAddressV2 + + // If the address is not routable, try again. + routable := false + for !routable { + addr = randAddr(t) + routable = IsRoutable(addr) + } + + return addr +} + // assertAddr ensures that the two addresses match. The timestamp is not // checked as it does not affect uniquely identifying a specific address. func assertAddr(t *testing.T, got, expected *wire.NetAddressV2) { @@ -104,9 +121,9 @@ func TestAddrManagerSerialization(t *testing.T) { expectedAddrs := make(map[string]*wire.NetAddressV2, numAddrs) for i := 0; i < numAddrs; i++ { - addr := randAddr(t) + addr := routableRandAddr(t) expectedAddrs[NetAddressKey(addr)] = addr - addrMgr.AddAddress(addr, randAddr(t)) + addrMgr.AddAddress(addr, routableRandAddr(t)) } // Now that the addresses have been added, we should be able to retrieve @@ -149,9 +166,9 @@ func TestAddrManagerV1ToV2(t *testing.T) { expectedAddrs := make(map[string]*wire.NetAddressV2, numAddrs) for i := 0; i < numAddrs; i++ { - addr := randAddr(t) + addr := routableRandAddr(t) expectedAddrs[NetAddressKey(addr)] = addr - addrMgr.AddAddress(addr, randAddr(t)) + addrMgr.AddAddress(addr, routableRandAddr(t)) } // Then, we'll persist these addresses to disk and restart the address From f2caa8fadcb21ab7656975f750f51c4222d64e41 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 15 Feb 2024 18:13:52 +0900 Subject: [PATCH 15/33] blockchain: don't rely on BlockHeightByHash for prune height calculations Since BlockHeightByHash only returns the heights for blocks that are in the main chain, when a block that is stale gets pruned, this will cause an error in the block height lookup and cause an error in block processing. Look up the node directly from the index and if the node isn't found, just skip that node. For utxoCache.lastFlushHash, if that isn't found, just force a flush. --- blockchain/utxocache.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index 07dc698a91..3261f057e5 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -617,6 +617,7 @@ func (b *BlockChain) InitConsistentState(tip *blockNode, interrupt <-chan struct // Set the last flush hash as it's the default value of 0s. s.lastFlushHash = tip.hash + s.lastFlushTime = time.Now() return err } @@ -725,22 +726,35 @@ func (b *BlockChain) InitConsistentState(tip *blockNode, interrupt <-chan struct // Example: if the last flush hash was at height 100 and one of the deleted blocks was at // height 98, this function will return true. func (b *BlockChain) flushNeededAfterPrune(deletedBlockHashes []chainhash.Hash) (bool, error) { - lastFlushHeight, err := b.BlockHeightByHash(&b.utxoCache.lastFlushHash) - if err != nil { - return false, err + node := b.index.LookupNode(&b.utxoCache.lastFlushHash) + if node == nil { + // If we couldn't find the node where we last flushed at, have the utxo cache + // flush to be safe and that will set the last flush hash again. + // + // This realistically should never happen as nodes are never deleted from + // the block index. This happening likely means that there's a hardware + // error which is something we can't recover from. The best that we can + // do here is to just force a flush and hope that the newly set + // lastFlushHash doesn't error. + return true, nil } + lastFlushHeight := node.Height() + // Loop through all the block hashes and find out what the highest block height // among the deleted hashes is. highestDeletedHeight := int32(-1) for _, deletedBlockHash := range deletedBlockHashes { - height, err := b.BlockHeightByHash(&deletedBlockHash) - if err != nil { - return false, err + node := b.index.LookupNode(&deletedBlockHash) + if node == nil { + // If we couldn't find this node, just skip it and try the next + // deleted hash. This might be a corruption in the database + // but there's nothing we can do here to address it except for + // moving onto the next block. + continue } - - if height > highestDeletedHeight { - highestDeletedHeight = height + if node.height > highestDeletedHeight { + highestDeletedHeight = node.height } } From a254998bc5e348a227cdb454ffca0e9e3857c4a1 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Tue, 20 Feb 2024 17:08:49 +0900 Subject: [PATCH 16/33] blockchain: change reorg utxo cache behavior The assumption in the previous code was incorrect in that we were assuming that the chainLock is held throughout the entire chain reorg. This is not the case since the chainLock is let go of during the callback to the subscribers. Because of this, we need to ensure that the utxo set is consistent on each block disconnect. To achieve this, additional flushes are added during block disconnects. Also the utxocache is no longer avoided during block connects and when we're checking for the validity of the block connects and disconnects as we can just use the cache instead of trying to avoid it. --- blockchain/chain.go | 69 +++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 60420022ac..19de74ab83 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -796,6 +796,15 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view return err } + // Flush the cache on every disconnect. Since the code for + // reorganization modifies the database directly, the cache + // will be left in an inconsistent state if we don't flush it + // prior to the dbPutUtxoView that happends below. + err = b.utxoCache.flush(dbTx, FlushRequired, state) + if err != nil { + return err + } + // Update the utxo set using the state of the utxo view. This // entails restoring all of the utxos spent and removing the new // ones created by the block. @@ -880,6 +889,9 @@ func countSpentOutputs(block *btcutil.Block) int { // // This function may modify node statuses in the block index without flushing. // +// This function never leaves the utxo set in an inconsistent state for block +// disconnects. +// // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error { // Nothing to do if no reorganize nodes were provided. @@ -887,15 +899,6 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error return nil } - // The rest of the reorg depends on all STXOs already being in the database - // so we flush before reorg. - err := b.db.Update(func(dbTx database.Tx) error { - return b.utxoCache.flush(dbTx, FlushRequired, b.BestSnapshot()) - }) - if err != nil { - return err - } - // Ensure the provided nodes match the current best chain. tip := b.bestChain.Tip() if detachNodes.Len() != 0 { @@ -957,7 +960,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Load all of the utxos referenced by the block that aren't // already in the view. - err = view.fetchInputUtxos(b.db, nil, block) + err = view.fetchInputUtxos(nil, b.utxoCache, block) if err != nil { return err } @@ -1024,7 +1027,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // checkConnectBlock gets skipped, we still need to update the UTXO // view. if b.index.NodeStatus(n).KnownValid() { - err = view.fetchInputUtxos(b.db, nil, block) + err = view.fetchInputUtxos(nil, b.utxoCache, block) if err != nil { return err } @@ -1061,11 +1064,21 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error newBest = n } + // Flush the utxo cache for the block disconnect below. The disconnect + // code assumes that it's directly modifying the database so the cache + // will be left in an inconsistent state. It needs to be flushed beforehand + // in order for that to not happen. + err := b.db.Update(func(dbTx database.Tx) error { + return b.utxoCache.flush(dbTx, FlushRequired, b.BestSnapshot()) + }) + if err != nil { + return err + } + // Reset the view for the actual connection code below. This is // required because the view was previously modified when checking if // the reorg would be successful and the connection code requires the - // view to be valid from the viewpoint of each block being connected or - // disconnected. + // view to be valid from the viewpoint of each block being disconnected. view = NewUtxoViewpoint() view.SetBestHash(&b.bestChain.Tip().hash) @@ -1076,7 +1089,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Load all of the utxos referenced by the block that aren't // already in the view. - err := view.fetchInputUtxos(b.db, nil, block) + err := view.fetchInputUtxos(nil, b.utxoCache, block) if err != nil { return err } @@ -1089,51 +1102,39 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error return err } - // Update the database and chain state. + // Update the database and chain state. The cache will be flushed + // here before the utxoview modifications happen to the database. err = b.disconnectBlock(n, block, view) if err != nil { return err } } - // Connect the new best chain blocks. + // Connect the new best chain blocks using the utxocache directly. It's more + // efficient and since we already checked that the blocks are correct and that + // the transactions connect properly, it's ok to access the cache. If we suddenly + // crash here, we are able to recover as well. for i, e := 0, attachNodes.Front(); e != nil; i, e = i+1, e.Next() { n := e.Value.(*blockNode) block := attachBlocks[i] - // Load all of the utxos referenced by the block that aren't - // already in the view. - err := view.fetchInputUtxos(b.db, nil, block) - if err != nil { - return err - } - // Update the view to mark all utxos referenced by the block // as spent and add all transactions being created by this block // to it. Also, provide an stxo slice so the spent txout // details are generated. stxos := make([]SpentTxOut, 0, countSpentOutputs(block)) - err = view.connectTransactions(block, &stxos) + err := b.utxoCache.connectTransactions(block, &stxos) if err != nil { return err } // Update the database and chain state. - err = b.connectBlock(n, block, view, stxos) + err = b.connectBlock(n, block, nil, stxos) if err != nil { return err } } - // We call the flush at the end to update the last flush hash to the new - // best tip. - err = b.db.Update(func(dbTx database.Tx) error { - return b.utxoCache.flush(dbTx, FlushRequired, b.BestSnapshot()) - }) - if err != nil { - return err - } - // Log the point where the chain forked and old and new best chain // heads. if forkNode != nil { From 78b158dc566ef7773c823ac5dd8d363cd86eb19a Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 6 Mar 2024 02:42:33 +0900 Subject: [PATCH 17/33] blockchain: get rid of database as an argument in fetchInputUtxos Allowing the caller to fetch from either the database or the cache resulted in inconsistencies if the cache were ever to be dirty. Removing this option eliminates this problem. --- blockchain/chain.go | 6 +++--- blockchain/utxoviewpoint.go | 14 +++++--------- blockchain/validate.go | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 19de74ab83..fd07575a1d 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -960,7 +960,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Load all of the utxos referenced by the block that aren't // already in the view. - err = view.fetchInputUtxos(nil, b.utxoCache, block) + err = view.fetchInputUtxos(b.utxoCache, block) if err != nil { return err } @@ -1027,7 +1027,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // checkConnectBlock gets skipped, we still need to update the UTXO // view. if b.index.NodeStatus(n).KnownValid() { - err = view.fetchInputUtxos(nil, b.utxoCache, block) + err = view.fetchInputUtxos(b.utxoCache, block) if err != nil { return err } @@ -1089,7 +1089,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Load all of the utxos referenced by the block that aren't // already in the view. - err := view.fetchInputUtxos(nil, b.utxoCache, block) + err := view.fetchInputUtxos(b.utxoCache, block) if err != nil { return err } diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index fdd165c095..1e687431ae 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -666,15 +666,11 @@ func (view *UtxoViewpoint) findInputsToFetch(block *btcutil.Block) []wire.OutPoi // fetchInputUtxos loads the unspent transaction outputs for the inputs // referenced by the transactions in the given block into the view from the -// database or the cache as needed. In particular, referenced entries that -// are earlier in the block are added to the view and entries that are already -// in the view are not modified. -func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, cache *utxoCache, block *btcutil.Block) error { - if cache != nil { - return view.fetchUtxosFromCache(cache, view.findInputsToFetch(block)) - } - // Request the input utxos from the cache. - return view.fetchUtxosMain(db, view.findInputsToFetch(block)) +// cache as needed. In particular, referenced entries that are earlier in +// the block are added to the view and entries that are already in the view +// are not modified. +func (view *UtxoViewpoint) fetchInputUtxos(cache *utxoCache, block *btcutil.Block) error { + return view.fetchUtxosFromCache(cache, view.findInputsToFetch(block)) } // NewUtxoViewpoint returns a new empty unspent transaction output view. diff --git a/blockchain/validate.go b/blockchain/validate.go index 83929810ee..641967fbf2 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -1084,7 +1084,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi // // These utxo entries are needed for verification of things such as // transaction inputs, counting pay-to-script-hashes, and scripts. - err := view.fetchInputUtxos(nil, b.utxoCache, block) + err := view.fetchInputUtxos(b.utxoCache, block) if err != nil { return err } From 99846b08055b018eb83a489c679621bcedac4a56 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 6 Mar 2024 02:44:51 +0900 Subject: [PATCH 18/33] blockchain: remove unused fetchUtxosMain() --- blockchain/utxoviewpoint.go | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 1e687431ae..702f99507c 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -519,41 +519,6 @@ func (view *UtxoViewpoint) commit() { } } -// fetchUtxosMain fetches unspent transaction output data about the provided -// set of outpoints from the point of view of the end of the main chain at the -// time of the call. -// -// Upon completion of this function, the view will contain an entry for each -// requested outpoint. Spent outputs, or those which otherwise don't exist, -// will result in a nil entry in the view. -func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints []wire.OutPoint) error { - // Nothing to do if there are no requested outputs. - if len(outpoints) == 0 { - return nil - } - - // Load the requested set of unspent transaction outputs from the point - // of view of the end of the main chain. - // - // NOTE: Missing entries are not considered an error here and instead - // will result in nil entries in the view. This is intentionally done - // so other code can use the presence of an entry in the store as a way - // to unnecessarily avoid attempting to reload it from the database. - return db.View(func(dbTx database.Tx) error { - utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName) - for i := range outpoints { - entry, err := dbFetchUtxoEntry(dbTx, utxoBucket, outpoints[i]) - if err != nil { - return err - } - - view.entries[outpoints[i]] = entry - } - - return nil - }) -} - // fetchUtxosFromCache fetches unspent transaction output data about the provided // set of outpoints from the point of view of the end of the main chain at the // time of the call. It attempts to fetch them from the cache and whatever entries From 8d1aa01c69aba6d5c0d2ce09711f2c1c1aa0cadd Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 7 Mar 2024 15:24:00 +0900 Subject: [PATCH 19/33] blockchain: add another mapslice duplicate entry case Duplicate entries are currently possible in the following scenario: 1: Add entries to the mapslice. 2: 1st map is full. Move onto the 2nd map. 3: Delete any entry in the first map. 4: Attempt to add an entry in the 2nd map. When attempting (4), the entry should just be overwritten but a duplicate gets added. --- blockchain/utxocache_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index 45116655e3..a66d6dc578 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -69,6 +69,26 @@ func TestMapSlice(t *testing.T) { t.Fatalf("expected len of %d, got %d", len(m), ms.length()) } + // Delete the first element in the first map. + ms.delete(test.keys[0]) + delete(m, test.keys[0]) + + // Try to insert the last element in the mapslice again. + ms.put(test.keys[len(test.keys)-1], &UtxoEntry{}, 0) + m[test.keys[len(test.keys)-1]] = &UtxoEntry{} + + // Check that the duplicate didn't make it in. + if len(m) != ms.length() { + t.Fatalf("expected len of %d, got %d", len(m), ms.length()) + } + + ms.put(test.keys[0], &UtxoEntry{}, 0) + m[test.keys[0]] = &UtxoEntry{} + + if len(m) != ms.length() { + t.Fatalf("expected len of %d, got %d", len(m), ms.length()) + } + for _, key := range test.keys { expected, found := m[key] if !found { From 059a668e888fd1d9a06d1c53d91117997ca51c4d Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 7 Mar 2024 15:27:44 +0900 Subject: [PATCH 20/33] blockchain: check all the maps first before adding an entry When attempting to insert an entry to the mapslice, we check all the underlying maps to ensure that the entry doesn't exist. --- blockchain/utxocache.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index 07dc698a91..ebc9dc73de 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -99,7 +99,8 @@ func (ms *mapSlice) put(op wire.OutPoint, entry *UtxoEntry, totalEntryMemory uin ms.mtx.Lock() defer ms.mtx.Unlock() - for i, maxNum := range ms.maxEntries { + // Look for the key in the maps. + for i := range ms.maxEntries { m := ms.maps[i] _, found := m[op] if found { @@ -107,6 +108,10 @@ func (ms *mapSlice) put(op wire.OutPoint, entry *UtxoEntry, totalEntryMemory uin m[op] = entry return // Return as we were successful in adding the entry. } + } + + for i, maxNum := range ms.maxEntries { + m := ms.maps[i] if len(m) >= maxNum { // Don't try to insert if the map already at max since // that'll force the map to allocate double the memory it's From 078815bcbccf34a43e6f92ca1644459cbac7e1b8 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 7 Mar 2024 15:43:48 +0900 Subject: [PATCH 21/33] blockchain: remove utxoview from the argument in connectBlock Since no code is now depending on accepting new blocks without the cache, we get rid of the option to do so. --- blockchain/chain.go | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index fd07575a1d..dc47fbd71f 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -569,7 +569,7 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, - view *UtxoViewpoint, stxos []SpentTxOut) error { + stxos []SpentTxOut) error { // Make sure it's extending the end of the best chain. prevHash := &block.MsgBlock().Header.PrevBlock @@ -611,18 +611,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, curTotalTxns+numTxns, CalcPastMedianTime(node), ) - // If a utxoviewpoint was passed in, we'll be writing that viewpoint - // directly to the database on disk. In order for the database to be - // consistent, we must flush the cache before writing the viewpoint. - if view != nil { - err = b.db.Update(func(dbTx database.Tx) error { - return b.utxoCache.flush(dbTx, FlushRequired, state) - }) - if err != nil { - return err - } - } - // Atomically insert info into the database. err = b.db.Update(func(dbTx database.Tx) error { // If the pruneTarget isn't 0, we should attempt to delete older blocks @@ -676,16 +664,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, return err } - // Update the utxo set using the state of the utxo view. This - // entails removing all of the utxos spent and adding the new - // ones created by the block. - // - // A nil viewpoint is a no-op. - err = dbPutUtxoView(dbTx, view) - if err != nil { - return err - } - // Update the transaction spend journal by adding a record for // the block that contains all txos spent by it. err = dbPutSpendJournalEntry(dbTx, block.Hash(), stxos) @@ -709,12 +687,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, return err } - // Prune fully spent entries and mark all entries in the view unmodified - // now that the modifications have been committed to the database. - if view != nil { - view.commit() - } - // This node is now the end of the best chain. b.bestChain.SetTip(node) @@ -1118,18 +1090,18 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error n := e.Value.(*blockNode) block := attachBlocks[i] - // Update the view to mark all utxos referenced by the block + // Update the cache to mark all utxos referenced by the block // as spent and add all transactions being created by this block // to it. Also, provide an stxo slice so the spent txout // details are generated. stxos := make([]SpentTxOut, 0, countSpentOutputs(block)) - err := b.utxoCache.connectTransactions(block, &stxos) + err = b.utxoCache.connectTransactions(block, &stxos) if err != nil { return err } // Update the database and chain state. - err = b.connectBlock(n, block, nil, stxos) + err = b.connectBlock(n, block, stxos) if err != nil { return err } @@ -1226,7 +1198,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla } // Connect the block to the main chain. - err = b.connectBlock(node, block, nil, stxos) + err = b.connectBlock(node, block, stxos) if err != nil { // If we got hit with a rule error, then we'll mark // that status of the block as invalid and flush the From 9d2184eb2d29846b087857168c248610cf6d2c12 Mon Sep 17 00:00:00 2001 From: SulaimanAminuBarkindo Date: Thu, 7 Mar 2024 08:45:10 +0100 Subject: [PATCH 22/33] Add check for maximum signature length in ecdsa.ParseDERSignature * Updated the `ecdsa.ParseDERSignature` to include the MaxSigLen constant, which defines the maximum length of a DER encoded signature. * The MaxSigLen constant is set to 72 bytes, as per the explanation in the comment. * A new test case has been added to test the functionality with a long signature. * The test case checks if signatures longer than the specified maximum length are properly handled. * The test ensures that signatures exceeding the maximum length are correctly identified as invalid. --- btcec/ecdsa/signature.go | 26 +++++++++++++++++++++----- btcec/ecdsa/signature_test.go | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/btcec/ecdsa/signature.go b/btcec/ecdsa/signature.go index 092e4ceb1c..44b84a78c9 100644 --- a/btcec/ecdsa/signature.go +++ b/btcec/ecdsa/signature.go @@ -37,10 +37,20 @@ var ( oneInitializer = []byte{0x01} ) -// MinSigLen is the minimum length of a DER encoded signature and is when both R -// and S are 1 byte each. -// 0x30 + <1-byte> + 0x02 + 0x01 + + 0x2 + 0x01 + -const MinSigLen = 8 +const ( + // MinSigLen is the minimum length of a DER encoded signature and is when both R + // and S are 1 byte each. + // 0x30 + <1-byte> + 0x02 + 0x01 + + 0x2 + 0x01 + + MinSigLen = 8 + + // MaxSigLen is the maximum length of a DER encoded signature and is + // when both R and S are 33 bytes each. It is 33 bytes because a + // 256-bit integer requires 32 bytes and an additional leading null byte + // might be required if the high bit is set in the value. + // + // 0x30 + <1-byte> + 0x02 + 0x21 + <33 bytes> + 0x2 + 0x21 + <33 bytes> + MaxSigLen = 72 +) // canonicalPadding checks whether a big-endian encoded integer could // possibly be misinterpreted as a negative number (even though OpenSSL @@ -68,9 +78,15 @@ func parseSig(sigStr []byte, der bool) (*Signature, error) { // 0x30 <0x02> 0x2 // . - if len(sigStr) < MinSigLen { + // The signature must adhere to the minimum and maximum allowed length. + totalSigLen := len(sigStr) + if totalSigLen < MinSigLen { return nil, errors.New("malformed signature: too short") } + if der && totalSigLen > MaxSigLen { + return nil, errors.New("malformed signature: too long") + } + // 0x30 index := 0 if sigStr[index] != 0x30 { diff --git a/btcec/ecdsa/signature_test.go b/btcec/ecdsa/signature_test.go index d2eebdc788..f36e15db89 100644 --- a/btcec/ecdsa/signature_test.go +++ b/btcec/ecdsa/signature_test.go @@ -333,6 +333,21 @@ var signatureTests = []signatureTest{ der: false, isValid: false, }, + { + name: "Long signature.", + sig: []byte{0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69, + 0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3, 0xa1, + 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, + 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, + 0x41, 0x02, 0x20, 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, + 0x07, 0xde, 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, + 0x9d, 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, 0x91, + 0x17, 0x90, 0xda, 0x42, 0xca, 0xaf, 0x19, 0x7d, 0xb4, + }, + der: true, + isValid: false, + }, } func TestSignatures(t *testing.T) { From b66f5b837916252a9da959955ca340451e4c1855 Mon Sep 17 00:00:00 2001 From: theedtron Date: Sat, 9 Mar 2024 04:41:41 +0300 Subject: [PATCH 23/33] multi: fix ioutil deprecated function update i/o functions to use os / io package functions instead --- addrmgr/addrmanager_internal_test.go | 5 ++--- btcutil/txsort/txsort_test.go | 4 ++-- cmd/btcctl/httpclient.go | 7 ++++--- cmd/gencerts/gencerts.go | 5 ++--- config_test.go | 9 ++++----- database/example_test.go | 3 +-- docs/json_rpc_api.md | 6 +++--- rpcclient/examples/btcdwebsockets/main.go | 4 ++-- rpcclient/examples/btcwalletwebsockets/main.go | 4 ++-- rpcserver.go | 4 ++-- txscript/bench_test.go | 4 ++-- txscript/reference_test.go | 12 ++++++------ 12 files changed, 32 insertions(+), 35 deletions(-) diff --git a/addrmgr/addrmanager_internal_test.go b/addrmgr/addrmanager_internal_test.go index b58ff4b469..ff19eae1d1 100644 --- a/addrmgr/addrmanager_internal_test.go +++ b/addrmgr/addrmanager_internal_test.go @@ -1,7 +1,6 @@ package addrmgr import ( - "io/ioutil" "math/rand" "net" "os" @@ -108,7 +107,7 @@ func TestAddrManagerSerialization(t *testing.T) { // We'll start by creating our address manager backed by a temporary // directory. - tempDir, err := ioutil.TempDir("", "addrmgr") + tempDir, err := os.MkdirTemp("", "addrmgr") if err != nil { t.Fatalf("unable to create temp dir: %v", err) } @@ -148,7 +147,7 @@ func TestAddrManagerV1ToV2(t *testing.T) { // We'll start by creating our address manager backed by a temporary // directory. - tempDir, err := ioutil.TempDir("", "addrmgr") + tempDir, err := os.MkdirTemp("", "addrmgr") if err != nil { t.Fatalf("unable to create temp dir: %v", err) } diff --git a/btcutil/txsort/txsort_test.go b/btcutil/txsort/txsort_test.go index dd2149294e..16a3e61c83 100644 --- a/btcutil/txsort/txsort_test.go +++ b/btcutil/txsort/txsort_test.go @@ -7,7 +7,7 @@ package txsort_test import ( "bytes" "encoding/hex" - "io/ioutil" + "os" "path/filepath" "testing" @@ -64,7 +64,7 @@ func TestSort(t *testing.T) { for _, test := range tests { // Load and deserialize the test transaction. filePath := filepath.Join("testdata", test.hexFile) - txHexBytes, err := ioutil.ReadFile(filePath) + txHexBytes, err := os.ReadFile(filePath) if err != nil { t.Errorf("ReadFile (%s): failed to read test file: %v", test.name, err) diff --git a/cmd/btcctl/httpclient.go b/cmd/btcctl/httpclient.go index 2a0f6dffd4..c7b4b7e3a1 100644 --- a/cmd/btcctl/httpclient.go +++ b/cmd/btcctl/httpclient.go @@ -6,9 +6,10 @@ import ( "crypto/x509" "encoding/json" "fmt" - "io/ioutil" + "io" "net" "net/http" + "os" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/go-socks/socks" @@ -37,7 +38,7 @@ func newHTTPClient(cfg *config) (*http.Client, error) { // Configure TLS if needed. var tlsConfig *tls.Config if !cfg.NoTLS && cfg.RPCCert != "" { - pem, err := ioutil.ReadFile(cfg.RPCCert) + pem, err := os.ReadFile(cfg.RPCCert) if err != nil { return nil, err } @@ -95,7 +96,7 @@ func sendPostRequest(marshalledJSON []byte, cfg *config) ([]byte, error) { } // Read the raw bytes and close the response. - respBytes, err := ioutil.ReadAll(httpResponse.Body) + respBytes, err := io.ReadAll(httpResponse.Body) httpResponse.Body.Close() if err != nil { err = fmt.Errorf("error reading json reply: %v", err) diff --git a/cmd/gencerts/gencerts.go b/cmd/gencerts/gencerts.go index 27c9ae385c..328d5ea714 100644 --- a/cmd/gencerts/gencerts.go +++ b/cmd/gencerts/gencerts.go @@ -6,7 +6,6 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -65,11 +64,11 @@ func main() { } // Write cert and key files. - if err = ioutil.WriteFile(certFile, cert, 0666); err != nil { + if err = os.WriteFile(certFile, cert, 0666); err != nil { fmt.Fprintf(os.Stderr, "cannot write cert: %v\n", err) os.Exit(1) } - if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { + if err = os.WriteFile(keyFile, key, 0600); err != nil { os.Remove(certFile) fmt.Fprintf(os.Stderr, "cannot write key: %v\n", err) os.Exit(1) diff --git a/config_test.go b/config_test.go index e54a9f5f20..42a0cd4b90 100644 --- a/config_test.go +++ b/config_test.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" "regexp" @@ -23,14 +22,14 @@ func TestCreateDefaultConfigFile(t *testing.T) { sampleConfigFile := filepath.Join(filepath.Dir(path), "sample-btcd.conf") // Setup a temporary directory - tmpDir, err := ioutil.TempDir("", "btcd") + tmpDir, err := os.MkdirTemp("", "btcd") if err != nil { t.Fatalf("Failed creating a temporary directory: %v", err) } testpath := filepath.Join(tmpDir, "test.conf") // copy config file to location of btcd binary - data, err := ioutil.ReadFile(sampleConfigFile) + data, err := os.ReadFile(sampleConfigFile) if err != nil { t.Fatalf("Failed reading sample config file: %v", err) } @@ -39,7 +38,7 @@ func TestCreateDefaultConfigFile(t *testing.T) { t.Fatalf("Failed obtaining app path: %v", err) } tmpConfigFile := filepath.Join(appPath, "sample-btcd.conf") - err = ioutil.WriteFile(tmpConfigFile, data, 0644) + err = os.WriteFile(tmpConfigFile, data, 0644) if err != nil { t.Fatalf("Failed copying sample config file: %v", err) } @@ -57,7 +56,7 @@ func TestCreateDefaultConfigFile(t *testing.T) { t.Fatalf("Failed to create a default config file: %v", err) } - content, err := ioutil.ReadFile(testpath) + content, err := os.ReadFile(testpath) if err != nil { t.Fatalf("Failed to read generated default config file: %v", err) } diff --git a/database/example_test.go b/database/example_test.go index b64baf2c8e..1110d0dbc3 100644 --- a/database/example_test.go +++ b/database/example_test.go @@ -7,7 +7,6 @@ package database_test import ( "bytes" "fmt" - "io/ioutil" "os" "path/filepath" @@ -123,7 +122,7 @@ func Example_blockStorageAndRetrieval() { // Typically you wouldn't want to remove the database right away like // this, nor put it in the temp directory, but it's done here to ensure // the example cleans up after itself. - dbPath, err := ioutil.TempDir("", "exampleblkstorage") + dbPath, err := os.MkdirTemp("", "exampleblkstorage") if err != nil { fmt.Println(err) return diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index 2c7d455457..fc32040da7 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -1121,7 +1121,7 @@ func main() { // generated by btcd when it starts the RPC server and doesn't already // have one. btcdHomeDir := btcutil.AppDataDir("btcd", false) - certs, err := ioutil.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) + certs, err := os.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } @@ -1185,7 +1185,7 @@ func main() { // generated by btcd when it starts the RPC server and doesn't already // have one. btcdHomeDir := btcutil.AppDataDir("btcd", false) - certs, err := ioutil.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) + certs, err := os.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } @@ -1288,7 +1288,7 @@ func main() { // generated by btcd when it starts the RPC server and doesn't already // have one. btcdHomeDir := btcutil.AppDataDir("btcd", false) - certs, err := ioutil.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) + certs, err := os.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } diff --git a/rpcclient/examples/btcdwebsockets/main.go b/rpcclient/examples/btcdwebsockets/main.go index e3f4c13e40..878526b076 100644 --- a/rpcclient/examples/btcdwebsockets/main.go +++ b/rpcclient/examples/btcdwebsockets/main.go @@ -5,7 +5,7 @@ package main import ( - "io/ioutil" + "os" "log" "path/filepath" "time" @@ -33,7 +33,7 @@ func main() { // Connect to local btcd RPC server using websockets. btcdHomeDir := btcutil.AppDataDir("btcd", false) - certs, err := ioutil.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) + certs, err := os.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } diff --git a/rpcclient/examples/btcwalletwebsockets/main.go b/rpcclient/examples/btcwalletwebsockets/main.go index 3cbd9a3667..a63ef3db91 100644 --- a/rpcclient/examples/btcwalletwebsockets/main.go +++ b/rpcclient/examples/btcwalletwebsockets/main.go @@ -5,7 +5,7 @@ package main import ( - "io/ioutil" + "os" "log" "path/filepath" "time" @@ -29,7 +29,7 @@ func main() { // Connect to local btcwallet RPC server using websockets. certHomeDir := btcutil.AppDataDir("btcwallet", false) - certs, err := ioutil.ReadFile(filepath.Join(certHomeDir, "rpc.cert")) + certs, err := os.ReadFile(filepath.Join(certHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } diff --git a/rpcserver.go b/rpcserver.go index d6f3167f1e..eaa5f05633 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4650,10 +4650,10 @@ func genCertPair(certFile, keyFile string) error { } // Write cert and key files. - if err = ioutil.WriteFile(certFile, cert, 0666); err != nil { + if err = os.WriteFile(certFile, cert, 0666); err != nil { return err } - if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { + if err = os.WriteFile(keyFile, key, 0600); err != nil { os.Remove(certFile) return err } diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 0d1aa91468..60b0d9e12e 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -7,7 +7,7 @@ package txscript import ( "bytes" "fmt" - "io/ioutil" + "os" "testing" "github.com/btcsuite/btcd/chaincfg" @@ -25,7 +25,7 @@ var ( func init() { // tx 620f57c92cf05a7f7e7f7d28255d5f7089437bc48e34dcfebf7751d08b7fb8f5 - txHex, err := ioutil.ReadFile("data/many_inputs_tx.hex") + txHex, err := os.ReadFile("data/many_inputs_tx.hex") if err != nil { panic(fmt.Sprintf("unable to read benchmark tx file: %v", err)) } diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 59acdb8da7..16f06c4f70 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -11,7 +11,7 @@ import ( "errors" "fmt" "io/fs" - "io/ioutil" + "os" "path/filepath" "strconv" "strings" @@ -490,7 +490,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { // TestScripts ensures all of the tests in script_tests.json execute with the // expected results as defined in the test data. func TestScripts(t *testing.T) { - file, err := ioutil.ReadFile("data/script_tests.json") + file, err := os.ReadFile("data/script_tests.json") if err != nil { t.Fatalf("TestScripts: %v\n", err) } @@ -521,7 +521,7 @@ func testVecF64ToUint32(f float64) uint32 { // TestTxInvalidTests ensures all of the tests in tx_invalid.json fail as // expected. func TestTxInvalidTests(t *testing.T) { - file, err := ioutil.ReadFile("data/tx_invalid.json") + file, err := os.ReadFile("data/tx_invalid.json") if err != nil { t.Fatalf("TestTxInvalidTests: %v\n", err) } @@ -679,7 +679,7 @@ testloop: // TestTxValidTests ensures all of the tests in tx_valid.json pass as expected. func TestTxValidTests(t *testing.T) { - file, err := ioutil.ReadFile("data/tx_valid.json") + file, err := os.ReadFile("data/tx_valid.json") if err != nil { t.Fatalf("TestTxValidTests: %v\n", err) } @@ -836,7 +836,7 @@ testloop: // in sighash.json. // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/sighash.json func TestCalcSignatureHash(t *testing.T) { - file, err := ioutil.ReadFile("data/sighash.json") + file, err := os.ReadFile("data/sighash.json") if err != nil { t.Fatalf("TestCalcSignatureHash: %v\n", err) } @@ -1044,7 +1044,7 @@ func TestTaprootReferenceTests(t *testing.T) { return nil } - testJson, err := ioutil.ReadFile(path) + testJson, err := os.ReadFile(path) if err != nil { return fmt.Errorf("unable to read file: %v", err) } From aee2705f701ae88a2de1035cd7bbec0debce602c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 8 Mar 2024 17:58:53 -0800 Subject: [PATCH 24/33] build: bump version to v0.24.2-beta.rc1 --- version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.go b/version.go index 999dfd95b2..0fd06fde6c 100644 --- a/version.go +++ b/version.go @@ -18,11 +18,11 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 24 - appPatch uint = 1 + appPatch uint = 2 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. - appPreRelease = "beta" + appPreRelease = "beta.rc1" ) // appBuild is defined as a variable so it can be overridden during the build From 36683e0b967f367b5781f0d28e4e242c7d17c9f6 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 15 Mar 2024 04:56:22 +0800 Subject: [PATCH 25/33] rpcclient: define specific errors for chain backends This commit adds detailed errors for all possible errors returned from `sendrawtransaction` or `testmempoolaccept`, enabling upstream callers to have refined control over the actions to be taken. --- rpcclient/errors.go | 572 +++++++++++++++++++++++++++++++-------- rpcclient/errors_test.go | 122 +++++++++ 2 files changed, 577 insertions(+), 117 deletions(-) create mode 100644 rpcclient/errors_test.go diff --git a/rpcclient/errors.go b/rpcclient/errors.go index 09e1cb3c22..68c0780dff 100644 --- a/rpcclient/errors.go +++ b/rpcclient/errors.go @@ -2,6 +2,7 @@ package rpcclient import ( "errors" + "fmt" "strings" ) @@ -15,163 +16,500 @@ var ( // parameter to an RPC method. ErrInvalidParam = errors.New("invalid param") - // RejectReasonMap takes the error returned from - // `CheckMempoolAcceptance` in `btcd` and maps it to the reject reason - // that's returned from calling `testmempoolaccept` in `bitcoind`. - // references: - // - https://github.com/bitcoin/bitcoin/blob/master/test/functional/data/invalid_txs.py - // - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_accept.py - // - https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp + // ErrUndefined is used when an error returned is not recognized. We + // should gradually increase our error types to avoid returning this + // error. + ErrUndefined = errors.New("undefined") +) + +// BitcoindRPCErr represents an error returned by bitcoind's RPC server. +type BitcoindRPCErr uint32 + +// This section defines all possible errors or reject reasons returned from +// bitcoind's `sendrawtransaction` or `testmempoolaccept` RPC. +const ( + // ErrMissingInputsOrSpent is returned when calling + // `sendrawtransaction` with missing inputs. + ErrMissingInputsOrSpent BitcoindRPCErr = iota + + // ErrMaxBurnExceeded is returned when calling `sendrawtransaction` + // with exceeding, falling short of, and equaling maxburnamount. + ErrMaxBurnExceeded + + // ErrMaxFeeExceeded can happen when passing a signed tx to + // `testmempoolaccept`, but the tx pays more fees than specified. + ErrMaxFeeExceeded + + // ErrTxAlreadyKnown is used in the `reject-reason` field of + // `testmempoolaccept` when a transaction is already in the blockchain. + ErrTxAlreadyKnown + + // ErrTxAlreadyConfirmed is returned as an error from + // `sendrawtransaction` when a transaction is already in the + // blockchain. + ErrTxAlreadyConfirmed + + // ErrMempoolConflict happens when RBF is not enabled yet the + // transaction conflicts with an unconfirmed tx. . // - // Errors not mapped in `btcd`: - // - deployment error from `validateSegWitDeployment`. - // - the error when total inputs is higher than max allowed value from - // `CheckTransactionInputs`. - // - the error when total outputs is higher than total inputs from - // `CheckTransactionInputs`. - // - errors from `CalcSequenceLock`. + // NOTE: RBF rule 1. + ErrMempoolConflict + + // ErrReplacementAddsUnconfirmed is returned when a transaction adds + // new unconfirmed inputs. // - // NOTE: This is not an exhaustive list of errors, but it covers the - // usage case of LND. + // NOTE: RBF rule 2. + ErrReplacementAddsUnconfirmed + + // ErrInsufficientFee is returned when fee rate used or fees paid + // doesn't meet the requirements. // - //nolint:lll - RejectReasonMap = map[string]string{ - // BIP125 related errors. - // - // When fee rate used or fees paid doesn't meet the - // requirements. - "replacement transaction has an insufficient fee rate": "insufficient fee", - "replacement transaction has an insufficient absolute fee": "insufficient fee", + // NOTE: RBF rule 3 or 4. + ErrInsufficientFee - // When a transaction causes too many transactions being - // replaced. This is set by `MAX_REPLACEMENT_CANDIDATES` in - // `bitcoind` and defaults to 100. - "replacement transaction evicts more transactions than permitted": "too many potential replacements", + // ErrTooManyReplacements is returned when a transaction causes too + // many transactions being replaced. This is set by + // `MAX_REPLACEMENT_CANDIDATES` in `bitcoind` and defaults to 100. + // + // NOTE: RBF rule 5. + ErrTooManyReplacements - // When a transaction adds new unconfirmed inputs. - "replacement transaction spends new unconfirmed input": "replacement-adds-unconfirmed", + // ErrMempoolMinFeeNotMet is returned when the transaction doesn't meet + // the minimum relay fee. + ErrMempoolMinFeeNotMet - // A transaction that spends conflicting tx outputs that are - // rejected. - "replacement transaction spends parent transaction": "bad-txns-spends-conflicting-tx", + // ErrConflictingTx is returned when a transaction that spends + // conflicting tx outputs that are rejected. + ErrConflictingTx - // A transaction that conflicts with an unconfirmed tx. Happens - // when RBF is not enabled. - "output already spent in mempool": "txn-mempool-conflict", + // ErrEmptyOutput is returned when a transaction has no outputs. + ErrEmptyOutput - // A transaction with no outputs. - "transaction has no outputs": "bad-txns-vout-empty", + // ErrEmptyInput is returned when a transaction has no inputs. + ErrEmptyInput - // A transaction with no inputs. - "transaction has no inputs": "bad-txns-vin-empty", + // ErrTxTooSmall is returned when spending a tiny transaction(in + // non-witness bytes) that is disallowed. + // + // NOTE: ErrTxTooLarge must be put after ErrTxTooSmall because it's a + // subset of ErrTxTooSmall. Otherwise, if bitcoind returns + // `tx-size-small`, it will be matched to ErrTxTooLarge. + ErrTxTooSmall - // A tiny transaction(in non-witness bytes) that is disallowed. - // TODO(yy): find/return this error in `btcd`. - // "": "tx-size-small", + // ErrDuplicateInput is returned when a transaction has duplicate + // inputs. + ErrDuplicateInput - // A transaction with duplicate inputs. - "transaction contains duplicate inputs": "bad-txns-inputs-duplicate", + // ErrEmptyPrevOut is returned when a non-coinbase transaction has + // coinbase-like outpoint. + ErrEmptyPrevOut - // A non-coinbase transaction with coinbase-like outpoint. - "transaction input refers to previous output that is null": "bad-txns-prevout-null", + // ErrBelowOutValue is returned when a transaction's output value is + // greater than its input value. + ErrBelowOutValue - // A transaction pays too little fee. - "fees which is under the required amount": "bad-txns-in-belowout", - "has insufficient priority": "bad-txns-in-belowout", - "has been rejected by the rate limiter due to low fees": "bad-txns-in-belowout", + // ErrNegativeOutput is returned when a transaction has negative output + // value. + ErrNegativeOutput - // A transaction with negative output value. - "transaction output has negative value": "bad-txns-vout-negative", + // ErrLargeOutput is returned when a transaction has too large output + // value. + ErrLargeOutput - // A transaction with too large output value. - "transaction output value is higher than max allowed value": "bad-txns-vout-toolarge", + // ErrLargeTotalOutput is returned when a transaction has too large sum + // of output values. + ErrLargeTotalOutput - // A transaction with too large sum of output values. - "total value of all transaction outputs exceeds max allowed value": "bad-txns-txouttotal-toolarge", + // ErrScriptVerifyFlag is returned when there is invalid OP_IF + // construction. + ErrScriptVerifyFlag - // TODO(yy): find/return this error in `btcd`. - // "": "mandatory-script-verify-flag-failed (Invalid OP_IF construction)", + // ErrTooManySigOps is returned when a transaction has too many sigops. + ErrTooManySigOps - // A transaction with too many sigops. - "sigop cost is too hight": "bad-txns-too-many-sigops", + // ErrInvalidOpcode is returned when a transaction has invalid OP + // codes. + ErrInvalidOpcode - // A transaction with invalid OP codes. - // TODO(yy): find/return this error in `btcd`. - // "": "disabled opcode", + // ErrTxAlreadyInMempool is returned when a transaction is in the + // mempool. + ErrTxAlreadyInMempool - // A transaction already in the blockchain. - "database contains entry for spent tx output": "txn-already-known", - "transaction already exists in blockchain": "txn-already-known", + // ErrMissingInputs is returned when a transaction has missing inputs, + // that never existed or only existed once in the past. + ErrMissingInputs - // A transaction in the mempool. - "already have transaction in mempool": "txn-already-in-mempool", + // ErrOversizeTx is returned when a transaction is too large. + ErrOversizeTx - // A transaction with missing inputs, that never existed or - // only existed once in the past. - "either does not exist or has already been spent": "missing-inputs", + // ErrCoinbaseTx is returned when the transaction is coinbase tx. + ErrCoinbaseTx - // A really large transaction. - "serialized transaction is too big": "bad-txns-oversize", + // ErrNonStandardVersion is returned when the transactions are not + // standard - a version currently non-standard. + ErrNonStandardVersion - // A coinbase transaction. - "transaction is an invalid coinbase": "coinbase", + // ErrNonStandardScript is returned when the transactions are not + // standard - non-standard script. + ErrNonStandardScript - // Some nonstandard transactions - a version currently - // non-standard. - "transaction version": "version", + // ErrBareMultiSig is returned when the transactions are not standard - + // bare multisig script (2-of-3). + ErrBareMultiSig - // Some nonstandard transactions - non-standard script. - "non-standard script form": "scriptpubkey", - "has a non-standard input": "scriptpubkey", + // ErrScriptSigNotPushOnly is returned when the transactions are not + // standard - not-pushonly scriptSig. + ErrScriptSigNotPushOnly - // Some nonstandard transactions - bare multisig script - // (2-of-3). - "milti-signature script": "bare-multisig", + // ErrScriptSigSize is returned when the transactions are not standard + // - too large scriptSig (>1650 bytes). + ErrScriptSigSize - // Some nonstandard transactions - not-pushonly scriptSig. - "signature script is not push only": "scriptsig-not-pushonly", + // ErrTxTooLarge is returned when the transactions are not standard - + // too large tx size. + ErrTxTooLarge - // Some nonstandard transactions - too large scriptSig (>1650 - // bytes). - "signature script size is larger than max allowed": "scriptsig-size", + // ErrDust is returned when the transactions are not standard - output + // too small. + ErrDust - // Some nonstandard transactions - too large tx size. - "weight of transaction is larger than max allowed": "tx-size", + // ErrMultiOpReturn is returned when the transactions are not standard + // - muiltiple OP_RETURNs. + ErrMultiOpReturn - // Some nonstandard transactions - output too small. - "payment is dust": "dust", + // ErrNonFinal is returned when spending a timelocked transaction that + // hasn't expired yet. + ErrNonFinal - // Some nonstandard transactions - muiltiple OP_RETURNs. - "more than one transaction output in a nulldata script": "multi-op-return", + // ErrNonBIP68Final is returned when a transaction that is locked by + // BIP68 sequence logic and not expired yet. + ErrNonBIP68Final - // A timelocked transaction. - "transaction is not finalized": "non-final", - "tried to spend coinbase transaction output": "non-final", + // ErrSameNonWitnessData is returned when another tx with the same + // non-witness data is already in the mempool. For instance, these two + // txns share the same `txid` but different `wtxid`. + ErrSameNonWitnessData - // A transaction that is locked by BIP68 sequence logic. - "transaction's sequence locks on inputs not met": "non-BIP68-final", + // ErrNonMandatoryScriptVerifyFlag is returned when passing a raw tx to + // `testmempoolaccept`, which gives the error followed by (Witness + // program hash mismatch). + ErrNonMandatoryScriptVerifyFlag - // Minimally-small transaction(in non-witness bytes) that is - // allowed. - // TODO(yy): find/return this error in `btcd`. - // "": "txn-same-nonwitness-data-in-mempools", - } + // errSentinel is used to indicate the end of the error list. This + // should always be the last error code. + errSentinel ) -// MapBtcdErrToRejectReason takes an error returned from -// `CheckMempoolAcceptance` and maps the error to a bitcoind reject reason. -func MapBtcdErrToRejectReason(err error) string { - // Get the error string and turn it into lower case. - btcErr := strings.ToLower(err.Error()) +// Error implements the error interface. It returns the error message defined +// in `bitcoind`. + +// Some of the dashes used in the original error string is removed, e.g. +// "missing-inputs" is now "missing inputs". This is ok since we will normalize +// the errors before matching. +// +// references: +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/rpc_rawtransaction.py#L342 +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/data/invalid_txs.py +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_accept.py +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_accept_wtxid.py +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_dust.py +// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_limit.py +// - https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp +func (r BitcoindRPCErr) Error() string { + switch r { + case ErrMissingInputsOrSpent: + return "bad-txns-inputs-missingorspent" + + case ErrMaxBurnExceeded: + return "Unspendable output exceeds maximum configured by user (maxburnamount)" + + case ErrMaxFeeExceeded: + return "max-fee-exceeded" + + case ErrTxAlreadyKnown: + return "txn-already-known" + + case ErrTxAlreadyConfirmed: + return "Transaction already in block chain" + + case ErrMempoolConflict: + return "txn mempool conflict" + + case ErrReplacementAddsUnconfirmed: + return "replacement adds unconfirmed" + + case ErrInsufficientFee: + return "insufficient fee" + + case ErrTooManyReplacements: + return "too many potential replacements" + + case ErrMempoolMinFeeNotMet: + return "mempool min fee not met" + + case ErrConflictingTx: + return "bad txns spends conflicting tx" + + case ErrEmptyOutput: + return "bad txns vout empty" + + case ErrEmptyInput: + return "bad txns vin empty" + + case ErrTxTooSmall: + return "tx size small" + + case ErrDuplicateInput: + return "bad txns inputs duplicate" + + case ErrEmptyPrevOut: + return "bad txns prevout null" + + case ErrBelowOutValue: + return "bad txns in belowout" + + case ErrNegativeOutput: + return "bad txns vout negative" + + case ErrLargeOutput: + return "bad txns vout toolarge" + + case ErrLargeTotalOutput: + return "bad txns txouttotal toolarge" + + case ErrScriptVerifyFlag: + return "mandatory script verify flag failed" + + case ErrTooManySigOps: + return "bad txns too many sigops" + + case ErrInvalidOpcode: + return "disabled opcode" + + case ErrTxAlreadyInMempool: + return "txn already in mempool" + + case ErrMissingInputs: + return "missing inputs" + + case ErrOversizeTx: + return "bad txns oversize" + + case ErrCoinbaseTx: + return "coinbase" + + case ErrNonStandardVersion: + return "version" + + case ErrNonStandardScript: + return "scriptpubkey" + + case ErrBareMultiSig: + return "bare multisig" + + case ErrScriptSigNotPushOnly: + return "scriptsig not pushonly" + + case ErrScriptSigSize: + return "scriptsig size" + + case ErrTxTooLarge: + return "tx size" + + case ErrDust: + return "dust" + case ErrMultiOpReturn: + return "multi op return" + + case ErrNonFinal: + return "non final" + + case ErrNonBIP68Final: + return "non BIP68 final" + + case ErrSameNonWitnessData: + return "txn-same-nonwitness-data-in-mempool" + + case ErrNonMandatoryScriptVerifyFlag: + return "non-mandatory-script-verify-flag" + } + + return "unknown error" +} + +// BtcdErrMap takes the errors returned from btcd's `testmempoolaccept` and +// `sendrawtransaction` RPCs and map them to the errors defined above, which +// are results from calling either `testmempoolaccept` or `sendrawtransaction` +// in `bitcoind`. +// +// Errors not mapped in `btcd`: +// - deployment error from `validateSegWitDeployment`. +// - the error when total inputs is higher than max allowed value from +// `CheckTransactionInputs`. +// - the error when total outputs is higher than total inputs from +// `CheckTransactionInputs`. +// - errors from `CalcSequenceLock`. +// +// NOTE: This is not an exhaustive list of errors, but it covers the +// usage case of LND. +// +//nolint:lll +var BtcdErrMap = map[string]error{ + // BIP125 related errors. + // + // When fee rate used or fees paid doesn't meet the requirements. + "replacement transaction has an insufficient fee rate": ErrInsufficientFee, + "replacement transaction has an insufficient absolute fee": ErrInsufficientFee, + + // When a transaction causes too many transactions being replaced. This + // is set by `MAX_REPLACEMENT_CANDIDATES` in `bitcoind` and defaults to + // 100. + "replacement transaction evicts more transactions than permitted": ErrTooManyReplacements, + + // When a transaction adds new unconfirmed inputs. + "replacement transaction spends new unconfirmed input": ErrReplacementAddsUnconfirmed, + + // A transaction that spends conflicting tx outputs that are rejected. + "replacement transaction spends parent transaction": ErrConflictingTx, + + // A transaction that conflicts with an unconfirmed tx. Happens when + // RBF is not enabled. + "output already spent in mempool": ErrMempoolConflict, + + // A transaction with no outputs. + "transaction has no outputs": ErrEmptyOutput, + + // A transaction with no inputs. + "transaction has no inputs": ErrEmptyInput, + + // A transaction with duplicate inputs. + "transaction contains duplicate inputs": ErrDuplicateInput, + + // A non-coinbase transaction with coinbase-like outpoint. + "transaction input refers to previous output that is null": ErrEmptyPrevOut, + + // A transaction pays too little fee. + "fees which is under the required amount": ErrMempoolMinFeeNotMet, + "has insufficient priority": ErrInsufficientFee, + "has been rejected by the rate limiter due to low fees": ErrInsufficientFee, + + // A transaction with negative output value. + "transaction output has negative value": ErrNegativeOutput, + + // A transaction with too large output value. + "transaction output value is higher than max allowed value": ErrLargeOutput, + + // A transaction with too large sum of output values. + "total value of all transaction outputs exceeds max allowed value": ErrLargeTotalOutput, + + // A transaction with too many sigops. + "sigop cost is too hight": ErrTooManySigOps, + + // A transaction already in the blockchain. + "database contains entry for spent tx output": ErrTxAlreadyKnown, + "transaction already exists in blockchain": ErrTxAlreadyConfirmed, + + // A transaction in the mempool. + "already have transaction in mempool": ErrTxAlreadyInMempool, + + // A transaction with missing inputs, that never existed or only + // existed once in the past. + "either does not exist or has already been spent": ErrMissingInputs, + "orphan transaction": ErrMissingInputs, + + // A really large transaction. + "serialized transaction is too big": ErrOversizeTx, + + // A coinbase transaction. + "transaction is an invalid coinbase": ErrCoinbaseTx, + + // Some nonstandard transactions - a version currently non-standard. + "transaction version": ErrNonStandardVersion, + + // Some nonstandard transactions - non-standard script. + "non-standard script form": ErrNonStandardScript, + "has a non-standard input": ErrNonStandardScript, + + // Some nonstandard transactions - bare multisig script + // (2-of-3). + "milti-signature script": ErrBareMultiSig, + + // Some nonstandard transactions - not-pushonly scriptSig. + "signature script is not push only": ErrScriptSigNotPushOnly, + + // Some nonstandard transactions - too large scriptSig (>1650 + // bytes). + "signature script size is larger than max allowed": ErrScriptSigSize, + + // Some nonstandard transactions - too large tx size. + "weight of transaction is larger than max allowed": ErrTxTooLarge, + + // Some nonstandard transactions - output too small. + "payment is dust": ErrDust, + + // Some nonstandard transactions - muiltiple OP_RETURNs. + "more than one transaction output in a nulldata script": ErrMultiOpReturn, + + // A timelocked transaction. + "transaction is not finalized": ErrNonFinal, + "tried to spend coinbase transaction output": ErrNonFinal, + + // A transaction that is locked by BIP68 sequence logic. + "transaction's sequence locks on inputs not met": ErrNonBIP68Final, + + // TODO(yy): find/return the following errors in `btcd`. + // + // A tiny transaction(in non-witness bytes) that is disallowed. + // "unmatched btcd error 1": ErrTxTooSmall, + // "unmatched btcd error 2": ErrScriptVerifyFlag, + // // A transaction with invalid OP codes. + // "unmatched btcd error 3": ErrInvalidOpcode, + // // Minimally-small transaction(in non-witness bytes) that is + // // allowed. + // "unmatched btcd error 4": ErrSameNonWitnessData, +} + +// MapRPCErr takes an error returned from calling RPC methods from various +// chain backend and map it to an defined error here. It uses the `BtcdErrMap` +// defined above, whose keys are btcd error strings and values are errors made +// from bitcoind error strings. +// +// NOTE: we assume neutrino shares the same error strings as btcd. +func MapRPCErr(rpcErr error) error { // Iterate the map and find the matching error. - for keyErr, rejectReason := range RejectReasonMap { - // Match the substring. - if strings.Contains(btcErr, keyErr) { - return rejectReason + for btcdErr, err := range BtcdErrMap { + // Match it against btcd's error first. + if matchErrStr(rpcErr, btcdErr) { + return err } } - // If there's no match, return the error string directly. - return btcErr + // If not found, try to match it against bitcoind's error. + for i := uint32(0); i < uint32(errSentinel); i++ { + err := BitcoindRPCErr(i) + if matchErrStr(rpcErr, err.Error()) { + return err + } + } + + // If not matched, return the original error wrapped. + return fmt.Errorf("%w: %v", ErrUndefined, rpcErr) +} + +// matchErrStr takes an error returned from RPC client and matches it against +// the specified string. If the expected string pattern is found in the error +// passed, return true. Both the error strings are normalized before matching. +func matchErrStr(err error, s string) bool { + // Replace all dashes found in the error string with spaces. + strippedErrStr := strings.ReplaceAll(err.Error(), "-", " ") + + // Replace all dashes found in the error string with spaces. + strippedMatchStr := strings.ReplaceAll(s, "-", " ") + + // Match against the lowercase. + return strings.Contains( + strings.ToLower(strippedErrStr), + strings.ToLower(strippedMatchStr), + ) } diff --git a/rpcclient/errors_test.go b/rpcclient/errors_test.go new file mode 100644 index 0000000000..e074622b11 --- /dev/null +++ b/rpcclient/errors_test.go @@ -0,0 +1,122 @@ +package rpcclient + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestMatchErrStr checks that `matchErrStr` can correctly replace the dashes +// with spaces and turn title cases into lowercases for a given error and match +// it against the specified string pattern. +func TestMatchErrStr(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + bitcoindErr error + matchStr string + matched bool + }{ + { + name: "error without dashes", + bitcoindErr: errors.New("missing input"), + matchStr: "missing input", + matched: true, + }, + { + name: "match str without dashes", + bitcoindErr: errors.New("missing-input"), + matchStr: "missing input", + matched: true, + }, + { + name: "error with dashes", + bitcoindErr: errors.New("missing-input"), + matchStr: "missing input", + matched: true, + }, + { + name: "match str with dashes", + bitcoindErr: errors.New("missing-input"), + matchStr: "missing-input", + matched: true, + }, + { + name: "error with title case and dash", + bitcoindErr: errors.New("Missing-Input"), + matchStr: "missing input", + matched: true, + }, + { + name: "match str with title case and dash", + bitcoindErr: errors.New("missing-input"), + matchStr: "Missing-Input", + matched: true, + }, + { + name: "unmatched error", + bitcoindErr: errors.New("missing input"), + matchStr: "missingorspent", + matched: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + matched := matchErrStr(tc.bitcoindErr, tc.matchStr) + require.Equal(t, tc.matched, matched) + }) + } +} + +// TestMapRPCErr checks that `MapRPCErr` can correctly map a given error to +// the corresponding error in the `BtcdErrMap` or `BitcoindErrors` map. +func TestMapRPCErr(t *testing.T) { + t.Parallel() + + require := require.New(t) + + // Get all known bitcoind errors. + bitcoindErrors := make([]error, 0, errSentinel) + for i := uint32(0); i < uint32(errSentinel); i++ { + err := BitcoindRPCErr(i) + bitcoindErrors = append(bitcoindErrors, err) + } + + // An unknown error should be mapped to ErrUndefined. + errUnknown := errors.New("unknown error") + err := MapRPCErr(errUnknown) + require.ErrorIs(err, ErrUndefined) + + // A known error should be mapped to the corresponding error in the + // `BtcdErrMap` or `bitcoindErrors` map. + for btcdErrStr, mappedErr := range BtcdErrMap { + err := MapRPCErr(errors.New(btcdErrStr)) + require.ErrorIs(err, mappedErr) + + err = MapRPCErr(mappedErr) + require.ErrorIs(err, mappedErr) + } + + for _, bitcoindErr := range bitcoindErrors { + err = MapRPCErr(bitcoindErr) + require.ErrorIs(err, bitcoindErr) + } +} + +// TestBitcoindErrorSentinel checks that all defined BitcoindRPCErr errors are +// added to the method `Error`. +func TestBitcoindErrorSentinel(t *testing.T) { + t.Parallel() + + rt := require.New(t) + + for i := uint32(0); i < uint32(errSentinel); i++ { + err := BitcoindRPCErr(i) + rt.NotEqualf(err.Error(), "unknown error", "error code %d is "+ + "not defined, make sure to update it inside the Error "+ + "method", i) + } +} From 8abf969c3cdc8d38cbfc522d05a01513f8917eee Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 15 Mar 2024 04:57:59 +0800 Subject: [PATCH 26/33] rpcserver: return the plain reject reason from `btcd` This commit changes the `RejectReason` resulted from calling btcd's `testmempoolaccept` to be un-matched, leaving it to the caller to decide when and where to match it. --- rpcserver.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index eaa5f05633..e5b8aac949 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -39,7 +39,6 @@ import ( "github.com/btcsuite/btcd/mining" "github.com/btcsuite/btcd/mining/cpuminer" "github.com/btcsuite/btcd/peer" - "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/websocket" @@ -3858,9 +3857,7 @@ func handleTestMempoolAccept(s *rpcServer, cmd interface{}, // TODO(yy): differentiate the errors and put package // error in `PackageError` field. - item.RejectReason = rpcclient.MapBtcdErrToRejectReason( - err, - ) + item.RejectReason = err.Error() results = append(results, item) From 2a225e9d36a1c70b45313aeef99774aa2d0c2f8b Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 6 Nov 2023 15:07:04 +0000 Subject: [PATCH 27/33] wire: add method TxID to MsgTx This commit adds a method to MsgTx called TxID. This method returns the transaction ID (txid) of the subject transaction. --- wire/msgtx.go | 5 +++++ wire/msgtx_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/wire/msgtx.go b/wire/msgtx.go index eab265c35d..a86061a95e 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -353,6 +353,11 @@ func (msg *MsgTx) TxHash() chainhash.Hash { return chainhash.DoubleHashRaw(msg.SerializeNoWitness) } +// TxID generates the transaction ID of the transaction. +func (msg *MsgTx) TxID() string { + return msg.TxHash().String() +} + // WitnessHash generates the hash of the transaction serialized according to // the new witness serialization defined in BIP0141 and BIP0144. The final // output is used within the Segregated Witness commitment of all the witnesses diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go index 5ec753b62d..1b3bfd4eec 100644 --- a/wire/msgtx_test.go +++ b/wire/msgtx_test.go @@ -756,6 +756,34 @@ func TestTxSerializeSizeStripped(t *testing.T) { } } +// TestTxID performs tests to ensure the serialize size for various transactions +// is accurate. +func TestTxID(t *testing.T) { + // Empty tx message. + noTx := NewMsgTx(1) + noTx.Version = 1 + + tests := []struct { + in *MsgTx // Tx to encode. + txid string // Expected transaction ID. + }{ + // No inputs or outputs. + {noTx, "d21633ba23f70118185227be58a63527675641ad37967e2aa461559f577aec43"}, + + // Transaction with an input and an output. + {multiTx, "0100d15a522ff38de05c164ca0a56379a1b77dd1e4805a6534dc9b3d88290e9d"}, + + // Transaction with an input which includes witness data, and + // one output. + {multiWitnessTx, "0f167d1385a84d1518cfee208b653fc9163b605ccf1b75347e2850b3e2eb19f3"}, + } + + for i, test := range tests { + txid := test.in.TxID() + require.Equal(t, test.txid, txid, "test #%d", i) + } +} + // TestTxWitnessSize performs tests to ensure that the serialized size for // various types of transactions that include witness data is accurate. func TestTxWitnessSize(t *testing.T) { From 75fe7e479030fdb4c2dfa0293e44afc74025fb00 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 9 Jun 2023 18:31:00 +0100 Subject: [PATCH 28/33] wire: make witnessToHex a TxWitness method called ToHexStrings --- rpcserver.go | 23 +++-------------------- wire/msgtx.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index e5b8aac949..09b5e48821 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -646,23 +646,6 @@ func handleDebugLevel(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) return "Done.", nil } -// witnessToHex formats the passed witness stack as a slice of hex-encoded -// strings to be used in a JSON response. -func witnessToHex(witness wire.TxWitness) []string { - // Ensure nil is returned when there are no entries versus an empty - // slice so it can properly be omitted as necessary. - if len(witness) == 0 { - return nil - } - - result := make([]string, 0, len(witness)) - for _, wit := range witness { - result = append(result, hex.EncodeToString(wit)) - } - - return result -} - // createVinList returns a slice of JSON objects for the inputs of the passed // transaction. func createVinList(mtx *wire.MsgTx) []btcjson.Vin { @@ -672,7 +655,7 @@ func createVinList(mtx *wire.MsgTx) []btcjson.Vin { txIn := mtx.TxIn[0] vinList[0].Coinbase = hex.EncodeToString(txIn.SignatureScript) vinList[0].Sequence = txIn.Sequence - vinList[0].Witness = witnessToHex(txIn.Witness) + vinList[0].Witness = txIn.Witness.ToHexStrings() return vinList } @@ -692,7 +675,7 @@ func createVinList(mtx *wire.MsgTx) []btcjson.Vin { } if mtx.HasWitness() { - vinEntry.Witness = witnessToHex(txIn.Witness) + vinEntry.Witness = txIn.Witness.ToHexStrings() } } @@ -3052,7 +3035,7 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P } if len(txIn.Witness) != 0 { - vinEntry.Witness = witnessToHex(txIn.Witness) + vinEntry.Witness = txIn.Witness.ToHexStrings() } // Add the entry to the list now if it already passed the filter diff --git a/wire/msgtx.go b/wire/msgtx.go index a86061a95e..61dbbe8995 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -5,6 +5,7 @@ package wire import ( + "encoding/hex" "errors" "fmt" "io" @@ -302,6 +303,22 @@ func (t TxWitness) SerializeSize() int { return n } +// ToHexStrings formats the witness stack as a slice of hex-encoded strings. +func (t TxWitness) ToHexStrings() []string { + // Ensure nil is returned when there are no entries versus an empty + // slice so it can properly be omitted as necessary. + if len(t) == 0 { + return nil + } + + result := make([]string, len(t)) + for idx, wit := range t { + result[idx] = hex.EncodeToString(wit) + } + + return result +} + // TxOut defines a bitcoin transaction output. type TxOut struct { Value int64 From 3cb9f602e825660078fd5d2035cbbf3ea5381f0c Mon Sep 17 00:00:00 2001 From: mattn Date: Mon, 25 Mar 2024 22:44:25 +0900 Subject: [PATCH 29/33] fix typos (#2100) --- CHANGES | 2 +- addrmgr/addrmanager_internal_test.go | 2 +- blockchain/chain_test.go | 8 ++++---- blockchain/chainio.go | 2 +- blockchain/chainio_test.go | 2 +- blockchain/common_test.go | 2 +- blockchain/difficulty_test.go | 2 +- blockchain/error.go | 2 +- blockchain/fullblocktests/generate.go | 4 ++-- blockchain/indexers/addrindex.go | 6 +++--- blockchain/merkle.go | 2 +- blockchain/upgrade_test.go | 2 +- blockchain/utxocache_test.go | 2 +- blockchain/utxoviewpoint.go | 4 ++-- blockchain/validate.go | 2 +- btcec/README.md | 2 +- btcec/ecdsa/signature.go | 2 +- btcec/field_test.go | 2 +- btcec/schnorr/musig2/context.go | 2 +- btcec/schnorr/musig2/musig2_test.go | 2 +- btcec/schnorr/musig2/nonces.go | 2 +- btcec/schnorr/musig2/sign_test.go | 4 ++-- btcjson/btcdextcmds.go | 2 +- btcjson/btcdextresults_test.go | 2 +- btcjson/chainsvrresults_test.go | 2 +- btcjson/chainsvrwsresults_test.go | 2 +- btcjson/cmdinfo_test.go | 2 +- btcjson/cmdparse.go | 2 +- btcjson/error.go | 2 +- btcutil/bech32/bech32.go | 2 +- btcutil/bech32/bech32_test.go | 4 ++-- btcutil/bloom/example_test.go | 2 +- btcutil/coinset/coins_test.go | 2 +- btcutil/gcs/builder/builder.go | 4 ++-- btcutil/psbt/finalizer.go | 8 ++++---- config.go | 2 +- connmgr/connmanager.go | 4 ++-- connmgr/seed.go | 2 +- database/error.go | 2 +- database/ffldb/driver.go | 2 +- database/ffldb/interface_test.go | 4 ++-- database/ffldb/whitebox_test.go | 2 +- database/interface.go | 2 +- docs/json_rpc_api.md | 2 +- log.go | 2 +- mining/mining.go | 2 +- mining/policy.go | 2 +- peer/example_test.go | 2 +- peer/peer.go | 2 +- rpcclient/chain_test.go | 6 +++--- rpcclient/infrastructure.go | 6 +++--- rpcclient/notify.go | 8 ++++---- rpcclient/rawtransactions.go | 2 +- rpcclient/wallet.go | 2 +- rpcserver.go | 12 ++++++------ rpcserverhelp.go | 2 +- rpcwebsocket.go | 12 ++++++------ service_windows.go | 2 +- txscript/data/script_tests.json | 2 +- txscript/data/tx_invalid.json | 2 +- txscript/engine.go | 2 +- txscript/engine_debug_test.go | 2 +- txscript/engine_test.go | 4 ++-- txscript/error.go | 2 +- txscript/script.go | 2 +- txscript/sigvalidate.go | 2 +- txscript/standard_test.go | 2 +- txscript/taproot.go | 2 +- txscript/taproot_test.go | 2 +- wire/bench_test.go | 4 ++-- wire/fixedIO_test.go | 4 ++-- wire/msgalert.go | 4 ++-- wire/msgblock.go | 2 +- wire/msgblock_test.go | 2 +- wire/msggetheaders.go | 2 +- wire/msgtx_test.go | 8 ++++---- wire/msgversion.go | 2 +- 77 files changed, 116 insertions(+), 116 deletions(-) diff --git a/CHANGES b/CHANGES index fd59a88672..4e359222b8 100644 --- a/CHANGES +++ b/CHANGES @@ -782,7 +782,7 @@ Changes in 0.8.0-beta (Sun May 25 2014) recent reference client changes (https://github.com/conformal/btcd/issues/100) - Raise the maximum signature script size to support standard 15-of-15 - multi-signature pay-to-sript-hash transactions with compressed pubkeys + multi-signature pay-to-script-hash transactions with compressed pubkeys to remain compatible with the reference client (https://github.com/conformal/btcd/issues/128) - Reduce max bytes allowed for a standard nulldata transaction to 40 for diff --git a/addrmgr/addrmanager_internal_test.go b/addrmgr/addrmanager_internal_test.go index ff19eae1d1..38218b15f7 100644 --- a/addrmgr/addrmanager_internal_test.go +++ b/addrmgr/addrmanager_internal_test.go @@ -184,7 +184,7 @@ func TestAddrManagerV1ToV2(t *testing.T) { addrMgr.loadPeers() addrs := addrMgr.getAddresses() if len(addrs) != len(expectedAddrs) { - t.Fatalf("expected to find %d adddresses, found %d", + t.Fatalf("expected to find %d addresses, found %d", len(expectedAddrs), len(addrs)) } for _, addr := range addrs { diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 1ac08f9a76..cd5c761bc4 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -782,7 +782,7 @@ func TestLocateInventory(t *testing.T) { &test.hashStop) } if !reflect.DeepEqual(headers, test.headers) { - t.Errorf("%s: unxpected headers -- got %v, want %v", + t.Errorf("%s: unexpected headers -- got %v, want %v", test.name, headers, test.headers) continue } @@ -795,7 +795,7 @@ func TestLocateInventory(t *testing.T) { hashes := chain.LocateBlocks(test.locator, &test.hashStop, maxAllowed) if !reflect.DeepEqual(hashes, test.hashes) { - t.Errorf("%s: unxpected hashes -- got %v, want %v", + t.Errorf("%s: unexpected hashes -- got %v, want %v", test.name, hashes, test.hashes) continue } @@ -888,7 +888,7 @@ func TestHeightToHashRange(t *testing.T) { } if !reflect.DeepEqual(hashes, test.hashes) { - t.Errorf("%s: unxpected hashes -- got %v, want %v", + t.Errorf("%s: unexpected hashes -- got %v, want %v", test.name, hashes, test.hashes) } } @@ -960,7 +960,7 @@ func TestIntervalBlockHashes(t *testing.T) { } if !reflect.DeepEqual(hashes, test.hashes) { - t.Errorf("%s: unxpected hashes -- got %v, want %v", + t.Errorf("%s: unexpected hashes -- got %v, want %v", test.name, hashes, test.hashes) } } diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 75474021f8..3340dd14a0 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -247,7 +247,7 @@ type SpentTxOut struct { // Amount is the amount of the output. Amount int64 - // PkScipt is the public key script for the output. + // PkScript is the public key script for the output. PkScript []byte // Height is the height of the block containing the creating tx. diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go index 630af14e1c..a9e19b6f01 100644 --- a/blockchain/chainio_test.go +++ b/blockchain/chainio_test.go @@ -403,7 +403,7 @@ func TestSpendJournalErrors(t *testing.T) { } // TestUtxoSerialization ensures serializing and deserializing unspent -// trasaction output entries works as expected. +// transaction output entries works as expected. func TestUtxoSerialization(t *testing.T) { t.Parallel() diff --git a/blockchain/common_test.go b/blockchain/common_test.go index 1973689ea1..5037c1828e 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -343,7 +343,7 @@ func (b *BlockChain) TstSetCoinbaseMaturity(maturity uint16) { b.chainParams.CoinbaseMaturity = maturity } -// newFakeChain returns a chain that is usable for syntetic tests. It is +// newFakeChain returns a chain that is usable for synthetic tests. It is // important to note that this chain has no database associated with it, so // it is not usable with all functions and the tests must take care when making // use of it. diff --git a/blockchain/difficulty_test.go b/blockchain/difficulty_test.go index 6fed37f136..c4d8fb6ef5 100644 --- a/blockchain/difficulty_test.go +++ b/blockchain/difficulty_test.go @@ -32,7 +32,7 @@ func TestBigToCompact(t *testing.T) { } // TestCompactToBig ensures CompactToBig converts numbers using the compact -// representation to the expected big intergers. +// representation to the expected big integers. func TestCompactToBig(t *testing.T) { tests := []struct { in uint32 diff --git a/blockchain/error.go b/blockchain/error.go index 1e7c879ba0..dc40222235 100644 --- a/blockchain/error.go +++ b/blockchain/error.go @@ -70,7 +70,7 @@ const ( // ErrUnexpectedDifficulty indicates specified bits do not align with // the expected value either because it doesn't match the calculated - // valued based on difficulty regarted rules or it is out of the valid + // valued based on difficulty regarded rules or it is out of the valid // range. ErrUnexpectedDifficulty diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index 15b92540b5..fe36bfe136 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -960,7 +960,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // --------------------------------------------------------------------- // The comments below identify the structure of the chain being built. // - // The values in parenthesis repesent which outputs are being spent. + // The values in parenthesis represent which outputs are being spent. // // For example, b1(0) indicates the first collected spendable output // which, due to the code above to create the correct number of blocks, @@ -1218,7 +1218,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { accepted() // --------------------------------------------------------------------- - // Multisig[Verify]/ChecksigVerifiy signature operation count tests. + // Multisig[Verify]/ChecksigVerify signature operation count tests. // --------------------------------------------------------------------- // Create block with max signature operations as OP_CHECKMULTISIG. diff --git a/blockchain/indexers/addrindex.go b/blockchain/indexers/addrindex.go index 7eaaab06b7..271e1665a7 100644 --- a/blockchain/indexers/addrindex.go +++ b/blockchain/indexers/addrindex.go @@ -64,7 +64,7 @@ const ( addrKeyTypeWitnessScriptHash = 3 // addrKeyTypeTaprootPubKey is the address type in an address key that - // represnts a pay-to-taproot address. We use this to denote addresses + // represents a pay-to-taproot address. We use this to denote addresses // related to the segwit v1 that are encoded in the bech32m format. addrKeyTypeTaprootPubKey = 4 @@ -158,7 +158,7 @@ func serializeAddrIndexEntry(blockID uint32, txLoc wire.TxLoc) []byte { // deserializeAddrIndexEntry decodes the passed serialized byte slice into the // provided region struct according to the format described in detail above and -// uses the passed block hash fetching function in order to conver the block ID +// uses the passed block hash fetching function in order to convert the block ID // to the associated block hash. func deserializeAddrIndexEntry(serialized []byte, region *database.BlockRegion, fetchBlockHash fetchBlockHashFunc) error { @@ -734,7 +734,7 @@ func (idx *AddrIndex) indexBlock(data writeIndexData, block *btcutil.Block, idx.indexPkScript(data, pkScript, txIdx) // With an input indexed, we'll advance the - // stxo coutner. + // stxo counter. stxoIndex++ } } diff --git a/blockchain/merkle.go b/blockchain/merkle.go index b89b518505..086c3643f6 100644 --- a/blockchain/merkle.go +++ b/blockchain/merkle.go @@ -146,7 +146,7 @@ func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash merkles[offset] = &newHash // The normal case sets the parent node to the double sha256 - // of the concatentation of the left and right children. + // of the concatenation of the left and right children. default: newHash := HashMerkleBranches(merkles[i], merkles[i+1]) merkles[offset] = &newHash diff --git a/blockchain/upgrade_test.go b/blockchain/upgrade_test.go index 97e7f55c35..9a060b3e8e 100644 --- a/blockchain/upgrade_test.go +++ b/blockchain/upgrade_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -// TestDeserializeUtxoEntryV0 ensures deserializing unspent trasaction output +// TestDeserializeUtxoEntryV0 ensures deserializing unspent transaction output // entries from the legacy version 0 format works as expected. func TestDeserializeUtxoEntryV0(t *testing.T) { tests := []struct { diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index c19850e4bf..0f410cc99e 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -445,7 +445,7 @@ func TestUtxoCacheFlush(t *testing.T) { t.Fatalf("Unexpected nil entry found for %v", outpoint) } if !entry.isModified() { - t.Fatal("Entry should be marked mofified") + t.Fatal("Entry should be marked modified") } if !entry.isFresh() { t.Fatal("Entry should be marked fresh") diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 702f99507c..f62f4b915f 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -163,13 +163,13 @@ type UtxoViewpoint struct { } // BestHash returns the hash of the best block in the chain the view currently -// respresents. +// represents. func (view *UtxoViewpoint) BestHash() *chainhash.Hash { return &view.bestHash } // SetBestHash sets the hash of the best block in the chain the view currently -// respresents. +// represents. func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) { view.bestHash = *hash } diff --git a/blockchain/validate.go b/blockchain/validate.go index 641967fbf2..6c49bd565d 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -879,7 +879,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode // // This function MUST be called with the chain state lock held (for reads). func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error { - // Fetch utxos for all of the transaction ouputs in this block. + // Fetch utxos for all of the transaction outputs in this block. // Typically, there will not be any utxos for any of the outputs. fetch := make([]wire.OutPoint, 0, len(block.Transactions())) for _, tx := range block.Transactions() { diff --git a/btcec/README.md b/btcec/README.md index cbf63dd045..533917736e 100644 --- a/btcec/README.md +++ b/btcec/README.md @@ -10,7 +10,7 @@ Bitcoin (secp256k1 only for now). It is designed so that it may be used with the standard crypto/ecdsa packages provided with go. A comprehensive suite of test is provided to ensure proper functionality. Package btcec was originally based on work from ThePiachu which is licensed under the same terms as Go, but it has -signficantly diverged since then. The btcsuite developers original is licensed +significantly diverged since then. The btcsuite developers original is licensed under the liberal ISC license. Although this package was primarily written for btcd, it has intentionally been diff --git a/btcec/ecdsa/signature.go b/btcec/ecdsa/signature.go index 44b84a78c9..11c6267caf 100644 --- a/btcec/ecdsa/signature.go +++ b/btcec/ecdsa/signature.go @@ -212,7 +212,7 @@ func parseSig(sigStr []byte, der bool) (*Signature, error) { } // ParseSignature parses a signature in BER format for the curve type `curve' -// into a Signature type, perfoming some basic sanity checks. If parsing +// into a Signature type, performing some basic sanity checks. If parsing // according to the more strict DER format is needed, use ParseDERSignature. func ParseSignature(sigStr []byte) (*Signature, error) { return parseSig(sigStr, false) diff --git a/btcec/field_test.go b/btcec/field_test.go index 6ade97a1eb..0844dc1d67 100644 --- a/btcec/field_test.go +++ b/btcec/field_test.go @@ -952,7 +952,7 @@ func TestFieldSquareRoot(t *testing.T) { input := setHex(test.in).Normalize() want := setHex(test.want).Normalize() - // Calculate the square root and enusre the validity flag matches the + // Calculate the square root and ensure the validity flag matches the // expected value. var result FieldVal isValid := result.SquareRootVal(input) diff --git a/btcec/schnorr/musig2/context.go b/btcec/schnorr/musig2/context.go index 8f4521502a..8e6b7154d3 100644 --- a/btcec/schnorr/musig2/context.go +++ b/btcec/schnorr/musig2/context.go @@ -513,7 +513,7 @@ func (s *Session) PublicNonce() [PubNonceSize]byte { } // NumRegisteredNonces returns the total number of nonces that have been -// regsitered so far. +// registered so far. func (s *Session) NumRegisteredNonces() int { return len(s.pubNonces) } diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 91dad90b3e..dfd48f3e82 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -258,7 +258,7 @@ func TestMuSigMultiParty(t *testing.T) { } // TestMuSigEarlyNonce tests that for protocols where nonces need to be -// exchagned before all signers are known, the context API works as expected. +// exchanged before all signers are known, the context API works as expected. func TestMuSigEarlyNonce(t *testing.T) { t.Parallel() diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index 988b199471..dbe39ef3db 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -144,7 +144,7 @@ func defaultNonceGenOpts() *nonceGenOpts { // WithCustomRand allows a caller to use a custom random number generator in // place for crypto/rand. This should only really be used to generate -// determinstic tests. +// deterministic tests. func WithCustomRand(r io.Reader) NonceGenOption { return func(o *nonceGenOpts) { o.randReader = r diff --git a/btcec/schnorr/musig2/sign_test.go b/btcec/schnorr/musig2/sign_test.go index a7f5d79d5d..a967cfe476 100644 --- a/btcec/schnorr/musig2/sign_test.go +++ b/btcec/schnorr/musig2/sign_test.go @@ -298,7 +298,7 @@ type sigCombineTestVectors struct { ValidCases []sigCombineValidCase `json:"valid_test_cases"` } -func pSigsFromIndicies(t *testing.T, sigs []string, indices []int) []*PartialSignature { +func pSigsFromIndices(t *testing.T, sigs []string, indices []int) []*PartialSignature { pSigs := make([]*PartialSignature, len(indices)) for i, idx := range indices { var pSig PartialSignature @@ -341,7 +341,7 @@ func TestMusig2SignCombine(t *testing.T) { t, testCase.NonceIndices, testCases.PubNonces, ) - partialSigs := pSigsFromIndicies( + partialSigs := pSigsFromIndices( t, testCases.Psigs, testCase.PSigIndices, ) diff --git a/btcjson/btcdextcmds.go b/btcjson/btcdextcmds.go index a3ca46ba71..768dca4d3d 100644 --- a/btcjson/btcdextcmds.go +++ b/btcjson/btcdextcmds.go @@ -20,7 +20,7 @@ const ( // persistent peer. NRemove NodeSubCmd = "remove" - // NDisconnect indicates the specified peer should be disonnected. + // NDisconnect indicates the specified peer should be disconnected. NDisconnect NodeSubCmd = "disconnect" ) diff --git a/btcjson/btcdextresults_test.go b/btcjson/btcdextresults_test.go index 478f088cd3..9a8b9eedf7 100644 --- a/btcjson/btcdextresults_test.go +++ b/btcjson/btcdextresults_test.go @@ -13,7 +13,7 @@ import ( ) // TestBtcdExtCustomResults ensures any results that have custom marshalling -// work as inteded. +// work as intended. // and unmarshal code of results are as expected. func TestBtcdExtCustomResults(t *testing.T) { t.Parallel() diff --git a/btcjson/chainsvrresults_test.go b/btcjson/chainsvrresults_test.go index 8a11197af2..2566e65f62 100644 --- a/btcjson/chainsvrresults_test.go +++ b/btcjson/chainsvrresults_test.go @@ -17,7 +17,7 @@ import ( ) // TestChainSvrCustomResults ensures any results that have custom marshalling -// work as inteded. +// work as intended. // and unmarshal code of results are as expected. func TestChainSvrCustomResults(t *testing.T) { t.Parallel() diff --git a/btcjson/chainsvrwsresults_test.go b/btcjson/chainsvrwsresults_test.go index b1e17450c1..9e8c7676c1 100644 --- a/btcjson/chainsvrwsresults_test.go +++ b/btcjson/chainsvrwsresults_test.go @@ -13,7 +13,7 @@ import ( ) // TestChainSvrWsResults ensures any results that have custom marshalling -// work as inteded. +// work as intended. func TestChainSvrWsResults(t *testing.T) { t.Parallel() diff --git a/btcjson/cmdinfo_test.go b/btcjson/cmdinfo_test.go index 61a693e404..9dc4567840 100644 --- a/btcjson/cmdinfo_test.go +++ b/btcjson/cmdinfo_test.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/btcjson" ) -// TestCmdMethod tests the CmdMethod function to ensure it retunrs the expected +// TestCmdMethod tests the CmdMethod function to ensure it returns the expected // methods and errors. func TestCmdMethod(t *testing.T) { t.Parallel() diff --git a/btcjson/cmdparse.go b/btcjson/cmdparse.go index 5cf3215e52..3b0473b865 100644 --- a/btcjson/cmdparse.go +++ b/btcjson/cmdparse.go @@ -232,7 +232,7 @@ func baseType(arg reflect.Type) (reflect.Type, int) { // assignField is the main workhorse for the NewCmd function which handles // assigning the provided source value to the destination field. It supports // direct type assignments, indirection, conversion of numeric types, and -// unmarshaling of strings into arrays, slices, structs, and maps via +// unmarshalling of strings into arrays, slices, structs, and maps via // json.Unmarshal. func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect.Value) error { // Just error now when the types have no chance of being compatible. diff --git a/btcjson/error.go b/btcjson/error.go index 3d72329f91..66be0214d1 100644 --- a/btcjson/error.go +++ b/btcjson/error.go @@ -30,7 +30,7 @@ const ( // embedded type which is not not supported. ErrEmbeddedType - // ErrUnexportedField indiciates the provided command struct contains an + // ErrUnexportedField indicates the provided command struct contains an // unexported field which is not supported. ErrUnexportedField diff --git a/btcutil/bech32/bech32.go b/btcutil/bech32/bech32.go index c1e00106e6..a7b056bd7a 100644 --- a/btcutil/bech32/bech32.go +++ b/btcutil/bech32/bech32.go @@ -18,7 +18,7 @@ const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} // toBytes converts each character in the string 'chars' to the value of the -// index of the correspoding character in 'charset'. +// index of the corresponding character in 'charset'. func toBytes(chars string) ([]byte, error) { decoded := make([]byte, 0, len(chars)) for i := 0; i < len(chars); i++ { diff --git a/btcutil/bech32/bech32_test.go b/btcutil/bech32/bech32_test.go index 1e04905a61..354ba7a17a 100644 --- a/btcutil/bech32/bech32_test.go +++ b/btcutil/bech32/bech32_test.go @@ -297,9 +297,9 @@ func TestMixedCaseEncode(t *testing.T) { } } -// TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works +// TestCanDecodeUnlimitedBech32 tests whether decoding a large bech32 string works // when using the DecodeNoLimit version -func TestCanDecodeUnlimtedBech32(t *testing.T) { +func TestCanDecodeUnlimitedBech32(t *testing.T) { input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd" // Sanity check that an input of this length errors on regular Decode() diff --git a/btcutil/bloom/example_test.go b/btcutil/bloom/example_test.go index e5a148a5ba..2be2b67a74 100644 --- a/btcutil/bloom/example_test.go +++ b/btcutil/bloom/example_test.go @@ -26,7 +26,7 @@ func ExampleNewFilter() { filter := bloom.NewFilter(10, tweak, 0.0001, wire.BloomUpdateNone) // Create a transaction hash and add it to the filter. This particular - // trasaction is the first transaction in block 310,000 of the main + // transaction is the first transaction in block 310,000 of the main // bitcoin block chain. txHashStr := "fd611c56ca0d378cdcd16244b45c2ba9588da3adac367c4ef43e808b280b8a45" txHash, err := chainhash.NewHashFromStr(txHashStr) diff --git a/btcutil/coinset/coins_test.go b/btcutil/coinset/coins_test.go index 035a40cb99..c1984623f6 100644 --- a/btcutil/coinset/coins_test.go +++ b/btcutil/coinset/coins_test.go @@ -252,7 +252,7 @@ func TestSimpleCoin(t *testing.T) { t.Error("Different value of coin pkScript than expected") } if testSimpleCoin.NumConfs() != 1 { - t.Error("Differet value of num confs than expected") + t.Error("Different value of num confs than expected") } if testSimpleCoin.ValueAge() != testSimpleCoinTxValueAge0 { t.Error("Different value of coin value * age than expected") diff --git a/btcutil/gcs/builder/builder.go b/btcutil/gcs/builder/builder.go index 3a85ad0519..4e952996c1 100644 --- a/btcutil/gcs/builder/builder.go +++ b/btcutil/gcs/builder/builder.go @@ -60,7 +60,7 @@ func RandomKey() ([gcs.KeySize]byte, error) { } // DeriveKey is a utility function that derives a key from a chainhash.Hash by -// truncating the bytes of the hash to the appopriate key size. +// truncating the bytes of the hash to the appropriate key size. func DeriveKey(keyHash *chainhash.Hash) [gcs.KeySize]byte { var key [gcs.KeySize]byte copy(key[:], keyHash.CloneBytes()) @@ -207,7 +207,7 @@ func (b *GCSBuilder) Build() (*gcs.Filter, error) { return nil, b.err } - // We'll ensure that all the parmaters we need to actually build the + // We'll ensure that all the paramaters we need to actually build the // filter properly are set. if b.p == 0 { return nil, fmt.Errorf("p value is not set, cannot build") diff --git a/btcutil/psbt/finalizer.go b/btcutil/psbt/finalizer.go index 3c2edd5557..b1bf12d131 100644 --- a/btcutil/psbt/finalizer.go +++ b/btcutil/psbt/finalizer.go @@ -404,7 +404,7 @@ func finalizeWitnessInput(p *Packet, inIndex int) error { } containsRedeemScript := pInput.RedeemScript != nil - cointainsWitnessScript := pInput.WitnessScript != nil + containsWitnessScript := pInput.WitnessScript != nil // If there's no redeem script, then we assume that this is native // segwit input. @@ -413,7 +413,7 @@ func finalizeWitnessInput(p *Packet, inIndex int) error { // If we have only a sigley pubkey+sig pair, and no witness // script, then we assume this is a P2WKH input. if len(pubKeys) == 1 && len(sigs) == 1 && - !cointainsWitnessScript { + !containsWitnessScript { serializedWitness, err = writePKHWitness( sigs[0], pubKeys[0], @@ -430,7 +430,7 @@ func finalizeWitnessInput(p *Packet, inIndex int) error { // TODO(roasbeef): need to add custom finalize for // non-multisig P2WSH outputs (HTLCs, delay outputs, // etc). - if !cointainsWitnessScript { + if !containsWitnessScript { return ErrNotFinalizable } @@ -457,7 +457,7 @@ func finalizeWitnessInput(p *Packet, inIndex int) error { // If don't have a witness script, then we assume this is a // nested p2wkh output. - if !cointainsWitnessScript { + if !containsWitnessScript { // Assumed p2sh-p2wkh Here the witness is just (sig, // pub) as for p2pkh case if len(sigs) != 1 || len(pubKeys) != 1 { diff --git a/config.go b/config.go index 18620a008c..f8aad70cf8 100644 --- a/config.go +++ b/config.go @@ -275,7 +275,7 @@ func parseAndSetDebugLevels(debugLevel string) error { // Validate subsystem. if _, exists := subsystemLoggers[subsysID]; !exists { str := "The specified subsystem [%v] is invalid -- " + - "supported subsytems %v" + "supported subsystems %v" return fmt.Errorf(str, subsysID, supportedSubsystems()) } diff --git a/connmgr/connmanager.go b/connmgr/connmanager.go index b487bd1ba1..e88f8af0cb 100644 --- a/connmgr/connmanager.go +++ b/connmgr/connmanager.go @@ -525,9 +525,9 @@ func (cm *ConnManager) Start() { // Start all the listeners so long as the caller requested them and // provided a callback to be invoked when connections are accepted. if cm.cfg.OnAccept != nil { - for _, listner := range cm.cfg.Listeners { + for _, listener := range cm.cfg.Listeners { cm.wg.Add(1) - go cm.listenHandler(listner) + go cm.listenHandler(listener) } } diff --git a/connmgr/seed.go b/connmgr/seed.go index 4c26160d8f..705618f778 100644 --- a/connmgr/seed.go +++ b/connmgr/seed.go @@ -23,7 +23,7 @@ const ( ) // OnSeed is the signature of the callback function which is invoked when DNS -// seeding is succesfull. +// seeding is successful. type OnSeed func(addrs []*wire.NetAddressV2) // LookupFunc is the signature of the DNS lookup function. diff --git a/database/error.go b/database/error.go index 49c250eef5..3470c49749 100644 --- a/database/error.go +++ b/database/error.go @@ -87,7 +87,7 @@ const ( // should be relatively, so this should rarely be an issue. ErrKeyTooLarge - // ErrValueTooLarge indicates an attmpt to insert a value that is larger + // ErrValueTooLarge indicates an attempt to insert a value that is larger // than max allowed value size. The max key size depends on the // specific backend driver being used. ErrValueTooLarge diff --git a/database/ffldb/driver.go b/database/ffldb/driver.go index 28ab8277e9..01290bf09a 100644 --- a/database/ffldb/driver.go +++ b/database/ffldb/driver.go @@ -78,7 +78,7 @@ func init() { UseLogger: useLogger, } if err := database.RegisterDriver(driver); err != nil { - panic(fmt.Sprintf("Failed to regiser database driver '%s': %v", + panic(fmt.Sprintf("Failed to register database driver '%s': %v", dbType, err)) } } diff --git a/database/ffldb/interface_test.go b/database/ffldb/interface_test.go index b0f275c5de..36db769b01 100644 --- a/database/ffldb/interface_test.go +++ b/database/ffldb/interface_test.go @@ -255,7 +255,7 @@ func testDeleteValues(tc *testContext, bucket database.Bucket, values []keyPair) return true } -// testCursorInterface ensures the cursor itnerface is working properly by +// testCursorInterface ensures the cursor interface is working properly by // exercising all of its functions on the passed bucket. func testCursorInterface(tc *testContext, bucket database.Bucket) bool { // Ensure a cursor can be obtained for the bucket. @@ -639,7 +639,7 @@ func rollbackOnPanic(t *testing.T, tx database.Tx) { func testMetadataManualTxInterface(tc *testContext) bool { // populateValues tests that populating values works as expected. // - // When the writable flag is false, a read-only tranasction is created, + // When the writable flag is false, a read-only transaction is created, // standard bucket tests for read-only transactions are performed, and // the Commit function is checked to ensure it fails as expected. // diff --git a/database/ffldb/whitebox_test.go b/database/ffldb/whitebox_test.go index cc7c13d45f..cac4984077 100644 --- a/database/ffldb/whitebox_test.go +++ b/database/ffldb/whitebox_test.go @@ -218,7 +218,7 @@ func TestCornerCases(t *testing.T) { ldb := idb.(*db).cache.ldb ldb.Close() - // Ensure initilization errors in the underlying database work as + // Ensure initialization errors in the underlying database work as // expected. testName = "initDB: reinitialization" wantErrCode = database.ErrDbNotOpen diff --git a/database/interface.go b/database/interface.go index 7efc7c55f6..7c4dd85122 100644 --- a/database/interface.go +++ b/database/interface.go @@ -390,7 +390,7 @@ type Tx interface { FetchBlockRegions(regions []BlockRegion) ([][]byte, error) // PruneBlocks deletes the block files until it reaches the target size - // (specificed in bytes). + // (specified in bytes). // // The interface contract guarantees at least the following errors will // be returned (other implementation-specific errors are possible): diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index fc32040da7..1999a6c245 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -472,7 +472,7 @@ Example Return|`{`
  `"bytes": 310768,`
  `"size": |---|---| |Method|help| |Parameters|1. command (string, optional) - the command to get help for| -|Description|Returns a list of all commands or help for a specified command.
When no `command` parameter is specified, a list of avaialable commands is returned
When `command` is a valid method, the help text for that method is returned.| +|Description|Returns a list of all commands or help for a specified command.
When no `command` parameter is specified, a list of available commands is returned
When `command` is a valid method, the help text for that method is returned.| |Returns|string| |Example Return|getblockcount
Returns a numeric for the number of blocks in the longest block chain.| [Return to Overview](#MethodOverview)
diff --git a/log.go b/log.go index 71accc7c9c..5707d7c23a 100644 --- a/log.go +++ b/log.go @@ -36,7 +36,7 @@ func (logWriter) Write(p []byte) (n int, err error) { return len(p), nil } -// Loggers per subsystem. A single backend logger is created and all subsytem +// Loggers per subsystem. A single backend logger is created and all subsystem // loggers created from it will write to the backend. When adding new // subsystems, add the subsystem logger variable here and to the // subsystemLoggers map. diff --git a/mining/mining.go b/mining/mining.go index 7905dade76..131c1207df 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -861,7 +861,7 @@ mempoolLoop: }, nil } -// AddWitnessCommitment adds the witness commitment as an OP_RETURN outpout +// AddWitnessCommitment adds the witness commitment as an OP_RETURN output // within the coinbase tx. The raw commitment is returned. func AddWitnessCommitment(coinbaseTx *btcutil.Tx, blockTxns []*btcutil.Tx) []byte { diff --git a/mining/policy.go b/mining/policy.go index 6213c2b336..8ddd575462 100644 --- a/mining/policy.go +++ b/mining/policy.go @@ -112,7 +112,7 @@ func CalcPriority(tx *wire.MsgTx, utxoView *blockchain.UtxoViewpoint, nextBlockH // A compressed pubkey pay-to-script-hash redemption with a maximum len // signature is of the form: // [OP_DATA_73 <73-byte sig> + OP_DATA_35 + {OP_DATA_33 - // <33 byte compresed pubkey> + OP_CHECKSIG}] + // <33 byte compressed pubkey> + OP_CHECKSIG}] // // Thus 1 + 73 + 1 + 1 + 33 + 1 = 110 overhead := 0 diff --git a/peer/example_test.go b/peer/example_test.go index d4662a2b4c..850557b877 100644 --- a/peer/example_test.go +++ b/peer/example_test.go @@ -16,7 +16,7 @@ import ( ) // mockRemotePeer creates a basic inbound peer listening on the simnet port for -// use with Example_peerConnection. It does not return until the listner is +// use with Example_peerConnection. It does not return until the listener is // active. func mockRemotePeer() error { // Configure peer to act as a simnet node that offers no services. diff --git a/peer/peer.go b/peer/peer.go index aa66cea98f..195fc0b4fe 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -744,7 +744,7 @@ func (p *Peer) LastRecv() time.Time { // LocalAddr returns the local address of the connection. // -// This function is safe fo concurrent access. +// This function is safe for concurrent access. func (p *Peer) LocalAddr() net.Addr { var localAddr net.Addr if atomic.LoadInt32(&p.connected) != 0 { diff --git a/rpcclient/chain_test.go b/rpcclient/chain_test.go index ba76078a16..ad1fb7aa2a 100644 --- a/rpcclient/chain_test.go +++ b/rpcclient/chain_test.go @@ -54,7 +54,7 @@ func TestUnmarshalGetBlockChainInfoResultSoftForks(t *testing.T) { for _, test := range tests { success := t.Run(test.name, func(t *testing.T) { - // We'll start by unmarshaling the JSON into a struct. + // We'll start by unmarshalling the JSON into a struct. // The SoftForks and UnifiedSoftForks field should not // be set yet, as they are unmarshaled within a // different function. @@ -226,7 +226,7 @@ func TestClientConnectedToWSServerRunner(t *testing.T) { response := <-ch if &expectedResponse != response { - t.Fatalf("received unexepcted response") + t.Fatalf("received unexpected response") } // ensure the goroutine created in this test exists, @@ -236,7 +236,7 @@ func TestClientConnectedToWSServerRunner(t *testing.T) { }, } - // since these tests rely on concurrency, ensure there is a resonable timeout + // since these tests rely on concurrency, ensure there is a reasonable timeout // that they should run within for _, testCase := range testTable { done := make(chan bool) diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 39cfa3f474..67f908efaa 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -116,7 +116,7 @@ type jsonRequest struct { type Client struct { id uint64 // atomic, so must stay 64-bit aligned - // config holds the connection configuration assoiated with this client. + // config holds the connection configuration associated with this client. config *ConnConfig // chainParams holds the params for the chain that this client is using, @@ -351,7 +351,7 @@ type Response struct { } // result checks whether the unmarshaled response contains a non-nil error, -// returning an unmarshaled btcjson.RPCError (or an unmarshaling error) if so. +// returning an unmarshaled btcjson.RPCError (or an unmarshalling error) if so. // If the response is not an error, the raw bytes of the request are // returned for further unmashaling into specific result types. func (r rawResponse) result() (result []byte, err error) { @@ -433,7 +433,7 @@ func (c *Client) handleMessage(msg []byte) { // to have come from reading from the websocket connection in wsInHandler, // should be logged. func (c *Client) shouldLogReadError(err error) bool { - // No logging when the connetion is being forcibly disconnected. + // No logging when the connection is being forcibly disconnected. select { case <-c.shutdown: return false diff --git a/rpcclient/notify.go b/rpcclient/notify.go index 1c2814c313..1f5cd48075 100644 --- a/rpcclient/notify.go +++ b/rpcclient/notify.go @@ -472,13 +472,13 @@ func (c *Client) handleNotification(ntfn *rawNotification) { } } -// wrongNumParams is an error type describing an unparseable JSON-RPC -// notificiation due to an incorrect number of parameters for the +// wrongNumParams is an error type describing an unparsable JSON-RPC +// notification due to an incorrect number of parameters for the // expected notification type. The value is the number of parameters // of the invalid notification. type wrongNumParams int -// Error satisifies the builtin error interface. +// Error satisfies the builtin error interface. func (e wrongNumParams) Error() string { return fmt.Sprintf("wrong number of parameters (%d)", e) } @@ -599,7 +599,7 @@ func parseFilteredBlockDisconnectedParams(params []json.RawMessage) (int32, return 0, nil, err } - // Unmarshal second parmeter as a slice of bytes. + // Unmarshal second parameter as a slice of bytes. blockHeaderBytes, err := parseHexParam(params[1]) if err != nil { return 0, nil, err diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 9c95041fd4..86073c464f 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -719,7 +719,7 @@ func (c *Client) SignRawTransactionWithWallet3Async(tx *wire.MsgTx, // // This function should only used if a non-default signature hash type is // desired. Otherwise, see SignRawTransactionWithWallet if the RPC server already -// knows the input transactions, or SignRawTransactionWihWallet2 if it does not. +// knows the input transactions, or SignRawTransactionWithWallet2 if it does not. func (c *Client) SignRawTransactionWithWallet3(tx *wire.MsgTx, inputs []btcjson.RawTxWitnessInput, hashType SigHashType) (*wire.MsgTx, bool, error) { diff --git a/rpcclient/wallet.go b/rpcclient/wallet.go index 7b7e7212c9..b8063c136d 100644 --- a/rpcclient/wallet.go +++ b/rpcclient/wallet.go @@ -2661,7 +2661,7 @@ func (c *Client) WalletCreateFundedPsbt( type FutureWalletProcessPsbtResult chan *Response // Receive waits for the Response promised by the future and returns an updated -// PSBT with signed inputs from the wallet and a boolen indicating if the +// PSBT with signed inputs from the wallet and a boolean indicating if the // transaction has a complete set of signatures. func (r FutureWalletProcessPsbtResult) Receive() (*btcjson.WalletProcessPsbtResult, error) { res, err := ReceiveFuture(r) diff --git a/rpcserver.go b/rpcserver.go index e5b8aac949..8fbb3e7352 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -846,7 +846,7 @@ func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{} // Get information about the script. // Ignore the error here since an error means the script couldn't parse - // and there is no additinal information about it anyways. + // and there is no additional information about it anyways. scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(script, s.cfg.ChainParams) addresses := make([]string, len(addrs)) @@ -3220,7 +3220,7 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan addressTxns := make([]retrievedTx, 0, numRequested) if reverse { // Transactions in the mempool are not in a block header yet, - // so the block header field in the retieved transaction struct + // so the block header field in the retrieved transaction struct // is left nil. mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr, uint32(numToSkip), uint32(numRequested)) @@ -3274,7 +3274,7 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan // order and the number of results is still under the number requested. if !reverse && len(addressTxns) < numRequested { // Transactions in the mempool are not in a block header yet, - // so the block header field in the retieved transaction struct + // so the block header field in the retrieved transaction struct // is left nil. mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr, uint32(numToSkip)-numSkipped, uint32(numRequested- @@ -4336,7 +4336,7 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin // change the read deadline for the new connection and having one breaks // long polling. However, not having a read deadline on the initial // connection would mean clients can connect and idle forever. Thus, - // hijack the connecton from the HTTP server, clear the read deadline, + // hijack the connection from the HTTP server, clear the read deadline, // and handle writing the response manually. hj, ok := w.(http.Hijacker) if !ok { @@ -4359,7 +4359,7 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin // Attempt to parse the raw body into a JSON-RPC request. // Setup a close notifier. Since the connection is hijacked, - // the CloseNotifer on the ResponseWriter is not available. + // the CloseNotifier on the ResponseWriter is not available. closeChan := make(chan struct{}, 1) go func() { _, err = conn.Read(make([]byte, 1)) @@ -4409,7 +4409,7 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin // Btcd does not respond to any request without and "id" or "id":null, // regardless the indicated JSON-RPC protocol version unless RPC quirks // are enabled. With RPC quirks enabled, such requests will be responded - // to if the reqeust does not indicate JSON-RPC version. + // to if the request does not indicate JSON-RPC version. // // RPC quirks can be enabled by the user to avoid compatibility issues // with software relying on Core's behavior. diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 0cc384db2c..1f8451a530 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -376,7 +376,7 @@ var helpDescsEnUS = map[string]string{ // GetCurrentNetCmd help. "getcurrentnet--synopsis": "Get bitcoin network the server is running on.", - "getcurrentnet--result0": "The network identifer", + "getcurrentnet--result0": "The network identifier", // GetDifficultyCmd help. "getdifficulty--synopsis": "Returns the proof-of-work difficulty as a multiple of the minimum difficulty.", diff --git a/rpcwebsocket.go b/rpcwebsocket.go index aedbcf90b6..592ad1f398 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -132,8 +132,8 @@ type wsNotificationManager struct { queueNotification chan interface{} // notificationMsgs feeds notificationHandler with notifications - // and client (un)registeration requests from a queue as well as - // registeration and unregisteration requests from clients. + // and client (un)registration requests from a queue as well as + // registration and unregistration requests from clients. notificationMsgs chan interface{} // Access channel for current number of connected clients. @@ -1236,7 +1236,7 @@ type wsResponse struct { // requested notifications to all connected websocket clients. Inbound // messages are read via the inHandler goroutine and generally dispatched to // their own handler. However, certain potentially long-running operations such -// as rescans, are sent to the asyncHander goroutine and are limited to one at a +// as rescans, are sent to the asyncHandler goroutine and are limited to one at a // time. There are two outbound message types - one for responding to client // requests and another for async notifications. Responses to client requests // use SendMessage which employs a buffered channel thereby limiting the number @@ -2144,7 +2144,7 @@ func handleNotifySpent(wsc *wsClient, icmd interface{}) (interface{}, error) { return nil, nil } -// handleNotifyNewTransations implements the notifynewtransactions command +// handleNotifyNewTransactions implements the notifynewtransactions command // extension for websocket connections. func handleNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{}, error) { cmd, ok := icmd.(*btcjson.NotifyNewTransactionsCmd) @@ -2157,7 +2157,7 @@ func handleNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{}, return nil, nil } -// handleStopNotifyNewTransations implements the stopnotifynewtransactions +// handleStopNotifyNewTransactions implements the stopnotifynewtransactions // command extension for websocket connections. func handleStopNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{}, error) { wsc.server.ntfnMgr.UnregisterNewMempoolTxsUpdates(wsc) @@ -2724,7 +2724,7 @@ fetchRange: // was any) still exists in the database. If it // doesn't, we error. // - // A goto is used to branch executation back to + // A goto is used to branch execution back to // before the range was evaluated, as it must be // reevaluated for the new hashList. minBlock += int32(i) diff --git a/service_windows.go b/service_windows.go index 378c9204f8..01edf3db77 100644 --- a/service_windows.go +++ b/service_windows.go @@ -153,7 +153,7 @@ func installService() error { // Support events to the event log using the standard "standard" Windows // EventCreate.exe message file. This allows easy logging of custom - // messges instead of needing to create our own message catalog. + // messages instead of needing to create our own message catalog. eventlog.Remove(svcName) eventsSupported := uint32(eventlog.Error | eventlog.Warning | eventlog.Info) return eventlog.InstallAsEventCreate(svcName, eventsSupported) diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index 5c054ed3e8..bd3b4e3125 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -666,7 +666,7 @@ ["0 0x02 0x0000 0", "CHECKMULTISIGVERIFY 1", "", "OK"], ["While not really correctly DER encoded, the empty signature is allowed by"], -["STRICTENC to provide a compact way to provide a delibrately invalid signature."], +["STRICTENC to provide a compact way to provide a deliberately invalid signature."], ["0", "0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 CHECKSIG NOT", "STRICTENC", "OK"], ["0 0", "1 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 1 CHECKMULTISIG NOT", "STRICTENC", "OK"], diff --git a/txscript/data/tx_invalid.json b/txscript/data/tx_invalid.json index db465109aa..85ceac145f 100644 --- a/txscript/data/tx_invalid.json +++ b/txscript/data/tx_invalid.json @@ -199,7 +199,7 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], "020000000100010000000000000000000000000000000000000000000000000000000000000000000000feff40000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], -["By-time locks, with argument just beyond txin.nSequence (but within numerical boundries)"], +["By-time locks, with argument just beyond txin.nSequence (but within numerical boundaries)"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194305 CHECKSEQUENCEVERIFY 1"]], "020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], diff --git a/txscript/engine.go b/txscript/engine.go index 30206152b8..1458728f72 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -1414,7 +1414,7 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error { func getStack(stack *stack) [][]byte { array := make([][]byte, stack.Depth()) for i := range array { - // PeekByteArry can't fail due to overflow, already checked + // PeekByteArray can't fail due to overflow, already checked array[len(array)-i-1], _ = stack.PeekByteArray(int32(i)) } return array diff --git a/txscript/engine_debug_test.go b/txscript/engine_debug_test.go index 5ebfe3f3cf..aa7283e22f 100644 --- a/txscript/engine_debug_test.go +++ b/txscript/engine_debug_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestDebugEngine checks that the StepCallbck called during debug script +// TestDebugEngine checks that the StepCallback called during debug script // execution contains the expected data. func TestDebugEngine(t *testing.T) { t.Parallel() diff --git a/txscript/engine_test.go b/txscript/engine_test.go index 51a899be33..c88d27a60e 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -123,12 +123,12 @@ func TestCheckErrorCondition(t *testing.T) { t.Fatalf("failed to step %dth time: %v", i, err) } if done { - t.Fatalf("finshed early on %dth time", i) + t.Fatalf("finished early on %dth time", i) } err = vm.CheckErrorCondition(false) if !IsErrorCode(err, ErrScriptUnfinished) { - t.Fatalf("got unexepected error %v on %dth iteration", + t.Fatalf("got unexpected error %v on %dth iteration", err, i) } } diff --git a/txscript/error.go b/txscript/error.go index 68bea7e879..a5ad0f2c4d 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -267,7 +267,7 @@ const ( ErrPubKeyType // ErrCleanStack is returned when the ScriptVerifyCleanStack flag - // is set, and after evalution, the stack does not contain only a + // is set, and after evaluation, the stack does not contain only a // single element. ErrCleanStack diff --git a/txscript/script.go b/txscript/script.go index 18723067ee..13d6c42711 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -244,7 +244,7 @@ func isCanonicalPush(opcode byte, data []byte) bool { // removeOpcodeByData will return the script minus any opcodes that perform a // canonical push of data that contains the passed data to remove. This // function assumes it is provided a version 0 script as any future version of -// script should avoid this functionality since it is unncessary due to the +// script should avoid this functionality since it is unnecessary due to the // signature scripts not being part of the witness-free transaction hash. // // WARNING: This will return the passed script unmodified unless a modification diff --git a/txscript/sigvalidate.go b/txscript/sigvalidate.go index 0bd00c326d..bda612a4e0 100644 --- a/txscript/sigvalidate.go +++ b/txscript/sigvalidate.go @@ -331,7 +331,7 @@ func newTaprootSigVerifier(pkBytes []byte, fullSigBytes []byte, // key and signature, and the passed sigHash as the message digest. func (t *taprootSigVerifier) verifySig(sigHash []byte) bool { // At this point, we can check to see if this signature is already - // included in the sigCcahe and is valid or not (if one was passed in). + // included in the sigCache and is valid or not (if one was passed in). cacheKey, _ := chainhash.NewHash(sigHash) if t.sigCache != nil { if t.sigCache.Exists(*cacheKey, t.fullSigBytes, t.pkBytes) { diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 283e2ccb7b..4993a65260 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -884,7 +884,7 @@ func TestMultiSigScript(t *testing.T) { } } -// TestCalcMultiSigStats ensures the CalcMutliSigStats function returns the +// TestCalcMultiSigStats ensures the CalcMultiSigStats function returns the // expected errors. func TestCalcMultiSigStats(t *testing.T) { t.Parallel() diff --git a/txscript/taproot.go b/txscript/taproot.go index 003eb19ae3..3776bf37a3 100644 --- a/txscript/taproot.go +++ b/txscript/taproot.go @@ -255,7 +255,7 @@ func ComputeTaprootOutputKey(pubKey *btcec.PublicKey, scriptRoot, ) - // With the tap tweek computed, we'll need to convert the merkle root + // With the tap tweak computed, we'll need to convert the merkle root // into something in the domain we can manipulate: a scalar value mod // N. var tweakScalar btcec.ModNScalar diff --git a/txscript/taproot_test.go b/txscript/taproot_test.go index 01b3780e9c..9c5bb573a4 100644 --- a/txscript/taproot_test.go +++ b/txscript/taproot_test.go @@ -224,7 +224,7 @@ func TestTaprootTweakNoMutation(t *testing.T) { return false } - // We shuold be able to re-derive the private key from raw + // We should be able to re-derive the private key from raw // bytes and have that match up again. privKeyCopy, _ := btcec.PrivKeyFromBytes(privBytes[:]) if *privKey != *privKeyCopy { diff --git a/wire/bench_test.go b/wire/bench_test.go index d19dd775f2..2f63fa30a6 100644 --- a/wire/bench_test.go +++ b/wire/bench_test.go @@ -549,7 +549,7 @@ func BenchmarkDeserializeTxSmall(b *testing.B) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0x07, // Varint for length of signature script 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script 0xff, 0xff, 0xff, 0xff, // Sequence @@ -671,7 +671,7 @@ func BenchmarkSerializeTxSmall(b *testing.B) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0x07, // Varint for length of signature script 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script 0xff, 0xff, 0xff, 0xff, // Sequence diff --git a/wire/fixedIO_test.go b/wire/fixedIO_test.go index ccd67ae411..0952a9b476 100644 --- a/wire/fixedIO_test.go +++ b/wire/fixedIO_test.go @@ -9,7 +9,7 @@ import ( "io" ) -// fixedWriter implements the io.Writer interface and intentially allows +// fixedWriter implements the io.Writer interface and intentionally allows // testing of error paths by forcing short writes. type fixedWriter struct { b []byte @@ -44,7 +44,7 @@ func newFixedWriter(max int) io.Writer { return &fw } -// fixedReader implements the io.Reader interface and intentially allows +// fixedReader implements the io.Reader interface and intentionally allows // testing of error paths by forcing short reads. type fixedReader struct { buf []byte diff --git a/wire/msgalert.go b/wire/msgalert.go index 71c4e220fe..b99ac89de9 100644 --- a/wire/msgalert.go +++ b/wire/msgalert.go @@ -83,7 +83,7 @@ const maxAlertSize = MaxMessagePayload - maxSignatureSize - MaxVarIntPayload - 1 // fit into a maximum size alert. // // maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string) -// for caculating maximum number of cancel IDs, set all other var sizes to 0 +// for calculating maximum number of cancel IDs, set all other var sizes to 0 // maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(int32) // x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4 const maxCountSetCancel = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4 @@ -92,7 +92,7 @@ const maxCountSetCancel = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) // fit into a maximum size alert. // // maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string) -// for caculating maximum number of subversions, set all other var sizes to 0 +// for calculating maximum number of subversions, set all other var sizes to 0 // maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(string) // x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / sizeOf(string) // subversion would typically be something like "/Satoshi:0.7.2/" (15 bytes) diff --git a/wire/msgblock.go b/wire/msgblock.go index d065e85c52..59dbbb1c06 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -245,7 +245,7 @@ func (msg *MsgBlock) Serialize(w io.Writer) error { // SerializeNoWitness encodes a block to w using an identical format to // Serialize, with all (if any) witness data stripped from all transactions. -// This method is provided in additon to the regular Serialize, in order to +// This method is provided in addition to the regular Serialize, in order to // allow one to selectively encode transaction witness data to non-upgraded // peers which are unaware of the new encoding. func (msg *MsgBlock) SerializeNoWitness(w io.Writer) error { diff --git a/wire/msgblock_test.go b/wire/msgblock_test.go index 2a861b208b..f0e938697c 100644 --- a/wire/msgblock_test.go +++ b/wire/msgblock_test.go @@ -562,7 +562,7 @@ var blockOneBytes = []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0x07, // Varint for length of signature script 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script (coinbase) 0xff, 0xff, 0xff, 0xff, // Sequence diff --git a/wire/msggetheaders.go b/wire/msggetheaders.go index f49e4c0dd4..38e5c6bfcd 100644 --- a/wire/msggetheaders.go +++ b/wire/msggetheaders.go @@ -23,7 +23,7 @@ import ( // // The algorithm for building the block locator hashes should be to add the // hashes in reverse order until you reach the genesis block. In order to keep -// the list of locator hashes to a resonable number of entries, first add the +// the list of locator hashes to a reasonable number of entries, first add the // most recent 10 block hashes, then double the step each loop iteration to // exponentially decrease the number of hashes the further away from head and // closer to the genesis block you get. diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go index 1b3bfd4eec..beba569b0f 100644 --- a/wire/msgtx_test.go +++ b/wire/msgtx_test.go @@ -672,7 +672,7 @@ func TestTxOverflowErrors(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Varint for length of signature script }, pver, BaseEncoding, txVer, &MessageError{}, @@ -688,7 +688,7 @@ func TestTxOverflowErrors(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0x00, // Varint for length of signature script 0xff, 0xff, 0xff, 0xff, // Sequence 0x01, // Varint for number of output transactions @@ -733,7 +733,7 @@ func TestTxSerializeSizeStripped(t *testing.T) { in *MsgTx // Tx to encode size int // Expected serialized size }{ - // No inputs or outpus. + // No inputs or outputs. {noTx, 10}, // Transcaction with an input and an output. @@ -938,7 +938,7 @@ var multiTxEncoded = []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash - 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0xff, 0xff, 0xff, 0xff, // Previous output index 0x07, // Varint for length of signature script 0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script 0xff, 0xff, 0xff, 0xff, // Sequence diff --git a/wire/msgversion.go b/wire/msgversion.go index 3077f12760..957bae395a 100644 --- a/wire/msgversion.go +++ b/wire/msgversion.go @@ -46,7 +46,7 @@ type MsgVersion struct { // connections. Nonce uint64 - // The user agent that generated messsage. This is a encoded as a varString + // The user agent that generated message. This is a encoded as a varString // on the wire. This has a max length of MaxUserAgentLen. UserAgent string From 6ae24a06936a6ffaa8d01e6fc416e85c3574616d Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Thu, 4 Jan 2024 11:40:16 +0800 Subject: [PATCH 30/33] mining: remove trailing continue --- mining/mining.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/mining/mining.go b/mining/mining.go index 7905dade76..bdfc8763fa 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -563,9 +563,6 @@ mempoolLoop: } prioItem.dependsOn[*originHash] = struct{}{} - // Skip the check below. We already know the - // referenced transaction is available. - continue } } From bbb6967247309eca4024c14c39f702940cc7d664 Mon Sep 17 00:00:00 2001 From: bruwbird Date: Thu, 21 Mar 2024 13:08:18 +0900 Subject: [PATCH 31/33] btcclient+btcjson: feeRate to BTC/kvB defaultMaxFeeRate was set to 1e8 / 10(sat/kb) as a parameter. But BTC/kvB is the expected value, so the units was wrong. This commit updates defaultMaxFeeRate to BTC/kvB and sets it to 0.1, which is the default value of Bitcoin Core. This commit also updates the comment to reflect the change. Because maxFeeRate sanity check has been added in bitcoin core v27.0 or later, sendrawtransaction cannot be executed without this change. --- btcjson/chainsvrcmds.go | 5 +++-- btcjson/chainsvrcmds_test.go | 8 ++++---- rpcclient/rawtransactions.go | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 956b4db604..fb690a19c3 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -862,7 +862,7 @@ func (a *AllowHighFeesOrMaxFeeRate) UnmarshalJSON(data []byte) error { case bool: a.Value = Bool(v) case float64: - a.Value = Int32(int32(v)) + a.Value = Float64(v) default: return fmt.Errorf("invalid allowhighfees or maxfeerate value: "+ "%v", unmarshalled) @@ -893,9 +893,10 @@ 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. +// maxFeeRate is the maximum fee rate for the transaction in BTC/kvB. // // A 0 maxFeeRate indicates that a maximum fee rate won't be enforced. -func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTransactionCmd { +func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate float64) *SendRawTransactionCmd { return &SendRawTransactionCmd{ HexTx: hexTx, FeeSetting: &AllowHighFeesOrMaxFeeRate{ diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index d3143a528c..38113a687e 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -1257,16 +1257,16 @@ func TestChainSvrCmds(t *testing.T) { { name: "sendrawtransaction optional, bitcoind >= 0.19.0", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("sendrawtransaction", "1122", &btcjson.AllowHighFeesOrMaxFeeRate{Value: btcjson.Int32(1234)}) + return btcjson.NewCmd("sendrawtransaction", "1122", &btcjson.AllowHighFeesOrMaxFeeRate{Value: btcjson.Float64(0.1234)}) }, staticCmd: func() interface{} { - return btcjson.NewBitcoindSendRawTransactionCmd("1122", 1234) + return btcjson.NewBitcoindSendRawTransactionCmd("1122", 0.1234) }, - marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",1234],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",0.1234],"id":1}`, unmarshalled: &btcjson.SendRawTransactionCmd{ HexTx: "1122", FeeSetting: &btcjson.AllowHighFeesOrMaxFeeRate{ - Value: btcjson.Int32(1234), + Value: btcjson.Float64(0.1234), }, }, }, diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 9c95041fd4..5834ccf202 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -17,9 +17,9 @@ import ( ) const ( - // defaultMaxFeeRate is the default maximum fee rate in sat/KB enforced + // defaultMaxFeeRate is the default maximum fee rate in BTC/kvB enforced // by bitcoind v0.19.0 or after for transaction broadcast. - defaultMaxFeeRate = btcutil.SatoshiPerBitcoin / 10 + defaultMaxFeeRate float64 = 0.1 ) // SigHashType enumerates the available signature hashing types that the @@ -365,7 +365,7 @@ func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) Fut if version.SupportUnifiedSoftForks() { // Using a 0 MaxFeeRate is interpreted as a maximum fee rate not // being enforced by bitcoind. - var maxFeeRate int32 + var maxFeeRate float64 if !allowHighFees { maxFeeRate = defaultMaxFeeRate } From 80b27f547157399c64b1717a4a528871bbfb7f5b Mon Sep 17 00:00:00 2001 From: bruwbird Date: Sat, 23 Mar 2024 11:14:22 +0900 Subject: [PATCH 32/33] btcclient+btcjson: add type alias for BTC/kvB Added type alias BTC/kvB to explicitly indicate that it represents the fee in BTC for a transaction size of 1 kB. Because bitcoind uses its own fee rate type (BTC/kvB instead of sat/kWU we use in lnd), define the type in btcjson package, as it's the only place where we actually use BTC/kvB. --- btcjson/chainsvrcmds.go | 14 +++++++++----- rpcclient/rawtransactions.go | 8 ++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index fb690a19c3..22552e7bcd 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -16,6 +16,10 @@ import ( "github.com/btcsuite/btcd/wire" ) +// BTCPerkvB is the units used to represent Bitcoin transaction fees. +// This unit represents the fee in BTC for a transaction size of 1 kB. +type BTCPerkvB = float64 + // AddNodeSubCmd defines the type used in the addnode JSON-RPC command for the // sub command field. type AddNodeSubCmd string @@ -142,7 +146,7 @@ type FundRawTransactionOpts struct { ChangeType *ChangeType `json:"change_type,omitempty"` IncludeWatching *bool `json:"includeWatching,omitempty"` LockUnspents *bool `json:"lockUnspents,omitempty"` - FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB + FeeRate *BTCPerkvB `json:"feeRate,omitempty"` // BTC/kB SubtractFeeFromOutputs []int `json:"subtractFeeFromOutputs,omitempty"` Replaceable *bool `json:"replaceable,omitempty"` ConfTarget *int `json:"conf_target,omitempty"` @@ -822,7 +826,7 @@ func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinE } // AllowHighFeesOrMaxFeeRate defines a type that can either be the legacy -// allowhighfees boolean field or the new maxfeerate int field. +// allowhighfees boolean field or the new maxfeerate float64 field. type AllowHighFeesOrMaxFeeRate struct { Value interface{} } @@ -896,7 +900,7 @@ func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransac // maxFeeRate is the maximum fee rate for the transaction in BTC/kvB. // // A 0 maxFeeRate indicates that a maximum fee rate won't be enforced. -func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate float64) *SendRawTransactionCmd { +func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate BTCPerkvB) *SendRawTransactionCmd { return &SendRawTransactionCmd{ HexTx: hexTx, FeeSetting: &AllowHighFeesOrMaxFeeRate{ @@ -1051,13 +1055,13 @@ type TestMempoolAcceptCmd struct { // Reject transactions whose fee rate is higher than the specified // value, expressed in BTC/kvB, optional, default="0.10". - MaxFeeRate float64 `json:"omitempty"` + MaxFeeRate BTCPerkvB `json:"omitempty"` } // NewTestMempoolAcceptCmd returns a new instance which can be used to issue a // testmempoolaccept JSON-RPC command. func NewTestMempoolAcceptCmd(rawTxns []string, - maxFeeRate float64) *TestMempoolAcceptCmd { + maxFeeRate BTCPerkvB) *TestMempoolAcceptCmd { return &TestMempoolAcceptCmd{ RawTxns: rawTxns, diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 5834ccf202..609d2a5100 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -19,7 +19,7 @@ import ( const ( // defaultMaxFeeRate is the default maximum fee rate in BTC/kvB enforced // by bitcoind v0.19.0 or after for transaction broadcast. - defaultMaxFeeRate float64 = 0.1 + defaultMaxFeeRate btcjson.BTCPerkvB = 0.1 ) // SigHashType enumerates the available signature hashing types that the @@ -365,7 +365,7 @@ func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) Fut if version.SupportUnifiedSoftForks() { // Using a 0 MaxFeeRate is interpreted as a maximum fee rate not // being enforced by bitcoind. - var maxFeeRate float64 + var maxFeeRate btcjson.BTCPerkvB if !allowHighFees { maxFeeRate = defaultMaxFeeRate } @@ -915,7 +915,7 @@ func (r FutureTestMempoolAcceptResult) Receive() ( // // See TestMempoolAccept for the blocking version and more details. func (c *Client) TestMempoolAcceptAsync(txns []*wire.MsgTx, - maxFeeRate float64) FutureTestMempoolAcceptResult { + maxFeeRate btcjson.BTCPerkvB) FutureTestMempoolAcceptResult { // Due to differences in the testmempoolaccept API for different // backends, we'll need to inspect our version and construct the @@ -1010,7 +1010,7 @@ func (c *Client) TestMempoolAcceptAsync(txns []*wire.MsgTx, // // The maximum number of transactions allowed is 25. func (c *Client) TestMempoolAccept(txns []*wire.MsgTx, - maxFeeRate float64) ([]*btcjson.TestMempoolAcceptResult, error) { + maxFeeRate btcjson.BTCPerkvB) ([]*btcjson.TestMempoolAcceptResult, error) { return c.TestMempoolAcceptAsync(txns, maxFeeRate).Receive() } From 95330bc1bb33eaf21dd850b99bb3d84bcfa03237 Mon Sep 17 00:00:00 2001 From: xiaoxiangxianzi <164908047+xiaoxiangxianzi@users.noreply.github.com> Date: Wed, 27 Mar 2024 21:45:48 +0800 Subject: [PATCH 33/33] chore: fix some comments (#2146) Signed-off-by: xiaoxiangxianzi --- blockchain/chain.go | 2 +- btcutil/gcs/builder/builder.go | 2 +- btcutil/psbt/creator.go | 2 +- btcutil/psbt/utils.go | 2 +- mempool/mempool.go | 2 +- netsync/manager.go | 2 +- rpcwebsocket.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index c7e8b3c36e..7e06e5c77c 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -773,7 +773,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view // Flush the cache on every disconnect. Since the code for // reorganization modifies the database directly, the cache // will be left in an inconsistent state if we don't flush it - // prior to the dbPutUtxoView that happends below. + // prior to the dbPutUtxoView that happens below. err = b.utxoCache.flush(dbTx, FlushRequired, state) if err != nil { return err diff --git a/btcutil/gcs/builder/builder.go b/btcutil/gcs/builder/builder.go index 4e952996c1..8e257b39fc 100644 --- a/btcutil/gcs/builder/builder.go +++ b/btcutil/gcs/builder/builder.go @@ -207,7 +207,7 @@ func (b *GCSBuilder) Build() (*gcs.Filter, error) { return nil, b.err } - // We'll ensure that all the paramaters we need to actually build the + // We'll ensure that all the parameters we need to actually build the // filter properly are set. if b.p == 0 { return nil, fmt.Errorf("p value is not set, cannot build") diff --git a/btcutil/psbt/creator.go b/btcutil/psbt/creator.go index a5f832e0dd..58b9a54488 100644 --- a/btcutil/psbt/creator.go +++ b/btcutil/psbt/creator.go @@ -17,7 +17,7 @@ const MinTxVersion = 1 // within the unsigned transaction. The values of nLockTime, nSequence (per // input) and transaction version (must be 1 of 2) must be specified here. Note // that the default nSequence value is wire.MaxTxInSequenceNum. Referencing -// the PSBT BIP, this function serves the roles of teh Creator. +// the PSBT BIP, this function serves the roles of the Creator. func New(inputs []*wire.OutPoint, outputs []*wire.TxOut, version int32, nLockTime uint32, nSequences []uint32) (*Packet, error) { diff --git a/btcutil/psbt/utils.go b/btcutil/psbt/utils.go index 85bc82f529..0a9002798e 100644 --- a/btcutil/psbt/utils.go +++ b/btcutil/psbt/utils.go @@ -226,7 +226,7 @@ func serializeKVPairWithType(w io.Writer, kt uint8, keydata []byte, // getKey retrieves a single key - both the key type and the keydata (if // present) from the stream and returns the key type as an integer, or -1 if -// the key was of zero length. This integer is is used to indicate the presence +// the key was of zero length. This integer is used to indicate the presence // of a separator byte which indicates the end of a given key-value pair list, // and the keydata as a byte slice or nil if none is present. func getKey(r io.Reader) (int, []byte, error) { diff --git a/mempool/mempool.go b/mempool/mempool.go index db04f619a5..af3830aeb3 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -1635,7 +1635,7 @@ func (mp *TxPool) validateStandardness(tx *btcutil.Tx, nextBlockHeight int32, } // validateSigCost checks the cost to run the signature operations to make sure -// the number of singatures are sane. +// the number of signatures are sane. func (mp *TxPool) validateSigCost(tx *btcutil.Tx, utxoView *blockchain.UtxoViewpoint) error { diff --git a/netsync/manager.go b/netsync/manager.go index b1a4db4d13..3215a86ace 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -1136,7 +1136,7 @@ func (sm *SyncManager) haveInventory(invVect *wire.InvVect) (bool, error) { return false, nil } - // The requested inventory is is an unsupported type, so just claim + // The requested inventory is an unsupported type, so just claim // it is known to avoid requesting it. return true, nil } diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 592ad1f398..02f59d58bf 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -228,7 +228,7 @@ func (m *wsNotificationManager) NotifyBlockDisconnected(block *btcutil.Block) { // NotifyMempoolTx passes a transaction accepted by mempool to the // notification manager for transaction notification processing. If -// isNew is true, the tx is is a new transaction, rather than one +// isNew is true, the tx is a new transaction, rather than one // added to the mempool during a reorg. func (m *wsNotificationManager) NotifyMempoolTx(tx *btcutil.Tx, isNew bool) { n := ¬ificationTxAcceptedByMempool{