Skip to content

Commit

Permalink
Restrict timestamp when mining a diff-adjustment block to prev-600
Browse files Browse the repository at this point in the history
This prepares us for a potential future timewarp-fixing softfork by
ensuring that we always refuse to mine blocks which refuse to
exploit timewarp, no matter the behavior of other miners. Note that
we allow timestamp to go backwards by 600 seconds on the
difficulty-adjustment blocks to avoid bricking any existing
hardware which relies on the ability to roll nTime by up to 600
seconds.

Note that it is possible that the eventual softfork to fix timewarp
has a looser resetriction than the 600 seconds enforced here,
however it seems unlikely we will apply a tighter one, and its fine
if we restrict things further on the mining end than in consensus.
  • Loading branch information
TheBlueMatt committed Feb 27, 2019
1 parent 0bf0b3c commit bdb4372
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam
{
int64_t nOldTime = pblock->nTime;
int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
if (pindexPrev->nHeight % consensusParams.DifficultyAdjustmentInterval() == consensusParams.DifficultyAdjustmentInterval() - 1) {
nNewTime = std::max(nNewTime, (int64_t)pindexPrev->nTime - 600);
}

if (nOldTime < nNewTime)
pblock->nTime = nNewTime;
Expand Down
6 changes: 5 additions & 1 deletion src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,11 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue);
result.pushKV("longpollid", chainActive.Tip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast));
result.pushKV("target", hashTarget.GetHex());
result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1);
if (pindexPrev->nHeight % consensusParams.DifficultyAdjustmentInterval() == consensusParams.DifficultyAdjustmentInterval() - 1) {
result.pushKV("mintime", std::max((int64_t)pindexPrev->GetMedianTimePast()+1, (int64_t)pindexPrev->nTime - 600));
} else {
result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1);
}
result.pushKV("mutable", aMutable);
result.pushKV("noncerange", "00000000ffffffff");
int64_t nSigOpLimit = MAX_BLOCK_SIGOPS_COST;
Expand Down
62 changes: 62 additions & 0 deletions test/functional/mining_timewarp_fork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test getblocktemplate complies with potential future timewarp-fixing
softforks (modulo 600 second nTime decrease).
We first mine a chain up to the difficulty-adjustment block and set
the last block's timestamp 2 hours in the future, then check the times
returned by getblocktemplate. Note that we cannot check the actual
retarget behavior as difficulty adjustments do not occur on regtest
(though it still technically has a difficulty adjustment interval)."""

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
connect_nodes_bi,
)


class TimewarpMiningTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True

def run_test(self):
self.log.info('Create some old blocks')
block_1 = self.nodes[0].generate(1)[0]
for _ in range(2013):
self.nodes[0].generate(1)
last_block_time = self.nodes[0].getblockheader(block_1)["time"] + 7199

self.log.info('Create a block 7199 seconds in the future')
self.nodes[0].setmocktime(last_block_time)
block_2015 = self.nodes[0].generate(1)[0]
assert_equal(self.nodes[0].getblockheader(block_2015)["time"], last_block_time)

mining_info = self.nodes[0].getmininginfo()
assert_equal(mining_info['blocks'], 2015)
assert_equal(mining_info['currentblocktx'], 0)
assert_equal(mining_info['currentblockweight'], 4000)

self.restart_node(0)
connect_nodes_bi(self.nodes, 0, 1)

self.log.info('Check that mintime and curtime are last-block - 600 seconds')
# Now test that mintime and curtime are last_block_time - 600
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert_equal(template["curtime"], last_block_time - 600)
assert_equal(template["mintime"], last_block_time - 600)

self.log.info('Generate a 2016th block and check that the next block\'s time goes back to now')
block_2016 = self.nodes[0].generate(1)[0]
assert_equal(self.nodes[0].getblockheader(block_2016)["time"], last_block_time - 600)

# Assume test doesn't take 2 hours and check that we let time jump backwards
# now that we mined the difficulty-adjustment block
block_2017 = self.nodes[0].generate(1)[0]
assert(self.nodes[0].getblockheader(block_2017)["time"] < last_block_time - 600)

if __name__ == '__main__':
TimewarpMiningTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
'wallet_backup.py',
# vv Tests less than 5m vv
'mining_getblocktemplate_longpoll.py',
'mining_timewarp_fork.py',
'feature_maxuploadtarget.py',
'feature_block.py',
'rpc_fundrawtransaction.py',
Expand Down

0 comments on commit bdb4372

Please sign in to comment.