#!/usr/bin/env python3
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php.

""" Test block announcement time tracking

The bitcoind client records, for each peer, the most recent time that
this peer announced a block that the client wasn't already aware of. This
timestamp, CNodeStateStats::m_last_block_announcement, is available in the
`last_block_announcement` field of each peer's `getpeerinfo` result. The
value zero means that this peer has never been the first to announce
a block to us. Blocks are announced using either the "headers" or
"cmpctblock" messages.

This timestamp is used when the "potential stale tip" condition occurs:
When a new block hasn't been seen for a longer-than-expected amount of
time (currently 30 minutes, see TipMayBeStale()), the client, suspecting
that there may be new blocks that its peers are not announcing, will
add an extra outbound peer and disconnect (evict) the peer that has
least recently been the first to announced a new block to us. (If there
is a tie, it will disconnect the most recently-added of those peers.)

This test verifies that this timestamp is being set correctly.
(This tests PR 26172.)
"""

import time
from test_framework.blocktools import (
    create_block,
    create_coinbase,
)
from test_framework.messages import (
    CBlockHeader,
    msg_headers,
)
from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework


class P2PBlockTimes(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.disable_autoconnect = False

    def run_test(self):
        node = self.nodes[0]
        cur_time = int(time.time())
        node.setmocktime(cur_time)

        # Generate one block to exit IBD
        self.generate(node, 1)

        self.log.info("Create a full-outbound test framework peer")
        node.add_outbound_p2p_connection(P2PDataStore(), p2p_idx=0)

        self.log.info("Test framework peer generates a new block")
        tip = int(node.getbestblockhash(), 16)
        block = create_block(tip, create_coinbase(2))
        block.solve()

        self.log.info("Check that last_block_announcement is initially zero")
        peerinfo = node.getpeerinfo()[0]
        assert peerinfo['last_block_announcement'] == 0

        self.log.info("Test framework peer sends node the new block")
        node.p2ps[0].send_blocks_and_test([block], node, success=True)

        self.log.info("Verify peerinfo block timestamps")
        peerinfo = node.getpeerinfo()[0]
        assert peerinfo['last_block'] == cur_time
        assert peerinfo['last_block_announcement'] == cur_time

        self.log.info("Sending a block announcement with no new blocks")
        node.setmocktime(cur_time+1)
        headers_message = msg_headers()
        headers_message.headers = [CBlockHeader(block)]
        node.p2ps[0].send_and_ping(headers_message)

        self.log.info("Verify that block announcement time isn't updated")
        peerinfo = node.getpeerinfo()[0]
        assert peerinfo['last_block_announcement'] == cur_time

if __name__ == '__main__':
    P2PBlockTimes(__file__).main()
