diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -113,12 +113,16 @@ static void CheckComparison(const uint256 &a, const uint256 &b) { BOOST_CHECK(a < b); + BOOST_CHECK(a <= b); BOOST_CHECK(b > a); + BOOST_CHECK(b >= a); } static void CheckComparison(const uint160 &a, const uint160 &b) { BOOST_CHECK(a < b); + BOOST_CHECK(a <= b); BOOST_CHECK(b > a); + BOOST_CHECK(b >= a); } // <= >= < > @@ -129,6 +133,8 @@ *(TmpL.begin() + (i >> 3)) |= 1 << (i & 7); CheckComparison(LastL, TmpL); LastL = TmpL; + BOOST_CHECK(LastL <= LastL); + BOOST_CHECK(LastL >= LastL); } CheckComparison(ZeroL, R1L); @@ -144,6 +150,8 @@ *(TmpS.begin() + (i >> 3)) |= 1 << (i & 7); CheckComparison(LastS, TmpS); LastS = TmpS; + BOOST_CHECK(LastS <= LastS); + BOOST_CHECK(LastS >= LastS); } CheckComparison(ZeroS, R1S); diff --git a/src/uint256.h b/src/uint256.h --- a/src/uint256.h +++ b/src/uint256.h @@ -61,9 +61,15 @@ friend inline bool operator<(const base_blob &a, const base_blob &b) { return a.Compare(b) < 0; } + friend inline bool operator<=(const base_blob &a, const base_blob &b) { + return a.Compare(b) <= 0; + } friend inline bool operator>(const base_blob &a, const base_blob &b) { return a.Compare(b) > 0; } + friend inline bool operator>=(const base_blob &a, const base_blob &b) { + return a.Compare(b) >= 0; + } std::string GetHex() const; void SetHex(const char *psz); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1715,7 +1715,12 @@ } if (fIsMagneticAnomalyEnabled || tx.IsCoinBase()) { - AddCoins(view, tx, pindex->nHeight); + // We do not need to throw when a transaction is duplicated. If they + // are in the same block, CheckBlock will catch it, and if they are + // in a different block, it'll register as a double spend or BIP30 + // violation. In both cases, we get a more meaningful feedback out + // of it. + AddCoins(view, tx, pindex->nHeight, true); } } @@ -3344,7 +3349,14 @@ for (const auto &ptx : block.vtx) { const CTransaction &tx = *ptx; if (fIsMagneticAnomalyEnabled) { - if (prevTx && (tx.GetId() < prevTx->GetId())) { + if (prevTx && (tx.GetId() <= prevTx->GetId())) { + if (tx.GetId() == prevTx->GetId()) { + return state.DoS(100, false, REJECT_INVALID, "tx-duplicate", + false, + strprintf("Duplicated transaction %s", + tx.GetId().ToString())); + } + return state.DoS( 100, false, REJECT_INVALID, "tx-ordering", false, strprintf("Transaction order is invalid (%s < %s)", diff --git a/test/functional/abc-transaction-ordering.py b/test/functional/abc-transaction-ordering.py --- a/test/functional/abc-transaction-ordering.py +++ b/test/functional/abc-transaction-ordering.py @@ -258,6 +258,39 @@ generatedblockhash = node.getbestblockhash() assert(forkblockhash != generatedblockhash) + # Reconstruct tip. + tip_hash = node.getbestblockhash() + self.tip = CBlock() + self.tip.sha256 = int(tip_hash, 16) + self.tip.nTime = timestamp = node.getblock(tip_hash)['time'] + self.block_heights[self.tip.sha256] = node.getblock(tip_hash)['height'] + + ordered_block(4446, out[18]) + yield accepted() + + # Generate a block with a duplicated transaction. + double_tx_block = ordered_block(4447, out[19]) + assert_equal(len(double_tx_block.vtx), 16) + double_tx_block.vtx = double_tx_block.vtx[:8] + \ + [double_tx_block.vtx[8]] + double_tx_block.vtx[8:] + update_block(4447) + yield rejected(RejectResult(16, b'bad-txns-duplicate')) + + # Rewind bad block. + tip(4446) + + # Check over two blocks. + proper_block = ordered_block(4448, out[20]) + yield accepted() + + replay_tx_block = ordered_block(4449, out[21]) + assert_equal(len(replay_tx_block.vtx), 16) + replay_tx_block.vtx.append(proper_block.vtx[5]) + replay_tx_block.vtx = [replay_tx_block.vtx[0]] + \ + sorted(replay_tx_block.vtx[1:], key=lambda tx: tx.get_id()) + update_block(4449) + yield rejected(RejectResult(16, b'bad-txns-BIP30')) + if __name__ == '__main__': TransactionOrderingTest().main()