6

I want to take the blocks between give timestamps. currently I manually use

fromBlock: (await web3.eth.getBlockNumber()) - 12343
toBlock: (await web3.eth.getBlockNumber()) - 6172

For getting yesterday's blocks. Is there any way to get the blocks using web3 between the timestamps?

4 Answers 4

4

If you want the blocks between timestamp1 <= block.timestamp <= timestamp2, you can do a binary search:

  • over the highest fromBlock you can find that satisfies web3.eth.getBlock(fromBlock).timestamp <= timestamp1
  • over the smallest toBlock you can find that satisfies timestamp2 <= web3.eth.getBlock(fromBlock).timestamp

Then, depending on your Web3 version, you do:

4

In case you need a binary search implementation, here it's for Python 3:

web3 = Web3(WebsocketProvider("...."))
a = 1484362538 # 01/14/2017 @ 2:55am (UTC)


def estimate_block_height_by_timestamp(timestamp):
    block_found = False
    last_block_number = 9682416
    close_in_seconds = 600

    while not block_found:
        block = web3.eth.getBlock(last_block_number)
        block_time = datetime.datetime.fromtimestamp(block.timestamp).date()
        difference_in_seconds = int((timestamp - block_time).total_seconds())

        block_found = abs(difference_in_seconds) < close_in_seconds

        if block_found:
            return last_block_number

        if difference_in_seconds < 0:
            last_block_number //= 2
        else:
            last_block_number = int(last_block_number * 1.5) + 1


block = estimate_block_height_by_timestamp(
    datetime.datetime.fromtimestamp(a).date()
)

print(block)

>>> 2994181
# https://etherscan.io/block/2994181
# Block time - Jan-14-2017 11:21:00 AM +UTC
1
  • It may be possible to use the fact that 1 block always takes ~x seconds to perform a much faster search...
    – P i
    Commented Aug 29, 2021 at 22:18
2

It's actually possible to use the fact that block-times are locally roughly linearly separated to optimize well beyond a binary search.

This code typically requires 5 or fewer fetches to find the nearest block to a given unix timestamp, and benchmarks ~10x faster than a binary search:

import  arrow

T = lambda i_block: web3.eth.getBlock(i_block).timestamp

ilatest = web3.eth.get_block('latest')['number']

def iblock_near(tunix_s, ipre=1, ipost=ilatest):
    ipre = max(1, ipre)
    ipost = min(ilatest, ipost)

    if ipre == ipost:
        print('Got it')
        return ipre

    t0, t1 = T(ipre), T(ipost)

    av_block_time = (t1 - t0) / (ipost-ipre)

    # if block-times were evenly-spaced, get expected block number
    k = (tunix_s - t0) / (t1-t0)
    iexpected = int(ipre + k * (ipost - ipre))

    # get the ACTUAL time for that block
    texpected = T(iexpected)

    # use the discrepancy to improve our guess
    est_nblocks_from_expected_to_target = int((tunix_s - texpected) / av_block_time)
    iexpected_adj = iexpected + est_nblocks_from_expected_to_target

    print()
    print(f'target timestamp ({tunix_s}) lies {k:.3f} of the way from block# {ipre} (t={t0}) to block# {ipost} (t={t1})')
    print(f'Expected block# assuming linearity: {iexpected} (t={texpected})')
    print('Expected nblocks required to reach target (again assuming linearity):', est_nblocks_from_expected_to_target)
    print('New guess at block #:', iexpected_adj)

    r = abs(est_nblocks_from_expected_to_target)

    return iblock_near(tunix_s, iexpected_adj - r, iexpected_adj + r)

Test:

import arrow

tunix_s = arrow.get('2021-03-11T12:34:56').timestamp()

block = iblock_near(tunix_s)

Output:

enter image description here

2

Pi's response is pretty good. But, I've ran into rare situations where the +r/-r excludes tunix_s, and then it recurses until recursion depth limit exceeded. These adjustments fix that issue:

def iblock_near(tunix_s, left_block_tuple=(1, web3.eth.get_block(1).timestamp), right_block_tuple=(latest_block_number, web3.eth.get_block('latest').timestamp)):
    left_block = left_block_tuple[0]
    right_block = right_block_tuple[0]
    left_timestamp = left_block_tuple[1]
    right_timestamp = right_block_tuple[1]

    if left_block == right_block:
        return left_block
    # Return the closer one, if we're already between blocks
    if left_block == right_block - 1 or tunix_s <= left_timestamp or tunix_s >= right_timestamp:
        return left_block if abs(tunix_s - left_block_tuple[1]) < abs(tunix_s - right_block_tuple[1]) else right_block

    # K is how far inbetween left and right we're expected to be
    k = (tunix_s - left_timestamp) / (right_timestamp - left_timestamp)
    # We bound, to ensure logarithmic time even when guesses aren't great
    k = min(max(k, 0.05), 0.95)
    # We get the expected block number from K
    expected_block = round(left_block + k * (right_block - left_block))
    # Make sure to make some progress
    expected_block = min(max(expected_block, left_block + 1), right_block - 1)

    # Get the actual timestamp for that block
    expected_block_timestamp = web3.eth.get_block(expected_block).timestamp

    # Adjust bound using our estimated block
    if expected_block_timestamp < tunix_s:
        left_block = expected_block
        left_timestamp = expected_block_timestamp
    elif expected_block_timestamp > tunix_s:
        right_block = expected_block
        right_timestamp = expected_block_timestamp
    else:
        # Return the perfect match
        return expected_block

    # Recurse using tightened bounds
    return iblock_near(tunix_s, (left_block, left_timestamp), (right_block, right_timestamp))

This modified version, caches the timestamp of 1 and latest, enforces logarithmic time convergence, and is thus safer to use and makes fewer requests as well.

tunix_s will also clamp to 1 or latest, if tunix_s is out-of-bounds for the eth network.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.