Endogenous Oracle: Specification

Jan 16, 2024

import pandas as pd
import hashlib
from binascii import unhexlify, hexlify

df = pd.read_csv('bitcoin_blocks.csv')


class Block:
    def __init__(self, version, previous_block_hash, merkle_root, time, bits , nonce):
        self.version = version
        self.previous_block_hash = previous_block_hash
        self.merkle_root = merkle_root
        self.time = time
        self.bits = bits
        self.nonce = nonce
    
    
    def __repr__(self):
        return f'Block(version={self.version}, previous_block_hash={self.previous_block_hash}, merkle_root={self.merkle_root}, time={self.time}, bits={self.bits}, nonce={self.nonce})'

    
    def toHash(self):
        headerHex = self.version + self.previous_block_hash + self.merkle_root + self.time + self.bits + self.nonce
        headerBin = unhexlify(headerHex)
        headerHash = hashlib.sha256(hashlib.sha256(headerBin).digest()).digest()
        return hexlify(headerHash).decode('utf-8')


# def toHex(i):
#     return hex(i)


# def formatHex(h):
#     return hex(int(h, 16))[2:]


def toTimestamp(x):
    return int(pd.to_datetime(x).timestamp())


def toTarget(b):
    big = bytearray(int(b, 16).to_bytes(4, 'little')).hex()
    x = int(big[:2], 16)
    y = int(big[2:], 16)
    return y* 2**(8*(x - 3))


def toDifficulty(t):
    return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / t
    
    
def readBlock(row):
    return Block(bytearray(int(row.version).to_bytes(4, 'little')).hex(),
                 bytearray(int(row.previous_block_hash, 16).to_bytes(32, 'little')).hex(),
                 bytearray(int(row.merkle_root, 16).to_bytes(32, 'little')).hex(),
                 bytearray(toTimestamp(row.time).to_bytes(4, 'little')).hex(),
                 bytearray(int(row.bits, 16).to_bytes(4, 'little')).hex(),
                 bytearray(int(row.nonce).to_bytes(4, 'little')).hex())


def flatten(xss):
    return [x for xs in xss for x in xs]


class Contract:
    def __init__(self, batchLength, startBlockNumber, startBlockHash, startTime):
        self.startBlockNumber = startBlockNumber
        self.batchLength = batchLength
        self.storedBlocks = [{'hash': startBlockHash,
                              'accumulatedDifficulty': 0,
                              'timestamp': startTime}]
        
    
    def __repr__(self):
        return f'''
Contract
startBlockNumber: {self.startBlockNumber}
batchLength: {self.batchLength}
storedBlocks: {self.storedBlocks}
    '''
    
    
    def submit(self, data, start):
        assert start > self.startBlockNumber
        assert (start - self.startBlockNumber - 1) % self.batchLength == 0
        
        index = (start - self.startBlockNumber - 1) // self.batchLength
        assert len(self.storedBlocks) > index
        prevHash = self.storedBlocks[index]['hash']
        accumulatedDifficulty = self.storedBlocks[-1]['accumulatedDifficulty']
        accumulatedDifficultyNew = self.storedBlocks[index]['accumulatedDifficulty']
            
        blocks = []
        for batch in data:
            assert len(batch) == self.batchLength
            for block in batch:
                assert prevHash == block.previous_block_hash
                blockHash = block.toHash()
                target = toTarget(block.bits)
                assert target >= int(bytearray(int(blockHash, 16).to_bytes(32, 'little')).hex(), 16)
                accumulatedDifficultyNew += toDifficulty(target)
                prevHash = blockHash
            blocks.append({'hash': blockHash,
                           'accumulatedDifficulty': accumulatedDifficultyNew,
                           'timestamp': int(bytearray(int(block.time, 16).to_bytes(4, 'little')).hex(), 16)})
                
        assert accumulatedDifficultyNew > accumulatedDifficulty
        self.storedBlocks = self.storedBlocks[: index + 1] + blocks
    
    
    
    def getBlock(self, blockNumber):
        assert blockNumber >= self.startBlockNumber
        assert (blockNumber - self.startBlockNumber) % self.batchLength == 0
        
        index = (blockNumber - self.startBlockNumber) // self.batchLength   
        assert len(self.storedBlocks) > index
        return self.storedBlocks[index]
    
    
    
if __name__ == '__main__':
    row0 = df.iloc[0]
    block0 = readBlock(row0)
    contract = Contract(10, int(row0.height), block0.toHash(), 1704068978)
    data = [[], []]
    for i in range(1, 11):
        row = df.iloc[i]
        data[0].append(readBlock(row))

    for i in range(11, 21):
        row = df.iloc[i]
        data[1].append(readBlock(row))
                
    contract.submit(data, 823786 + 1)
    
    data = [[]]
    for i in range(21, 31):
        row = df.iloc[i]
        data[0].append(readBlock(row))

    contract.submit(data, 823786 + 1 + 20)    

    data = [[], []]
    for i in range(21, 31):
        row = df.iloc[i]
        data[0].append(readBlock(row))

    for i in range(31, 41):
        row = df.iloc[i]
        data[1].append(readBlock(row))
        
    contract.submit(data, 823786 + 1 + 20)