bitcoindevkit / rust-electrum-client

Bitcoin Electrum client library. Supports plaintext, TLS and Onion servers.
MIT License
80 stars 62 forks source link

Bug: Wrong timestamps when using fulcrum #129

Closed andreasgriffin closed 2 months ago

andreasgriffin commented 9 months ago

Some of the timestamps of bdk list_transactions are wrong. Using bdkpython 0.31.0 I query my fulcrum mainnet bitcoin server (Fulcrum version: 1.9.7) with

import bdkpython as bdk 
import datetime

network = bdk.Network.BITCOIN
if network == bdk.Network.BITCOIN:
    blockchain_config = bdk.BlockchainConfig.ELECTRUM(
        bdk.ElectrumConfig(
            "my_fulcrum_server:50001", 
            None,
                10,
            10,
            100,
            False
        )
    ) 

blockchain = bdk.Blockchain(blockchain_config)

mnemonic = bdk.Mnemonic.from_string('bacon '*24)
print(mnemonic.as_string())

descriptor = bdk.Descriptor.new_bip84(
            secret_key=bdk.DescriptorSecretKey(network, mnemonic, ''),
             keychain=bdk.KeychainKind.EXTERNAL,
             network=network,
)

change_descriptor = bdk.Descriptor.new_bip84(
            secret_key=bdk.DescriptorSecretKey(network, mnemonic, ''),
             keychain=bdk.KeychainKind.INTERNAL,
             network=network,
)

print(f'xpub {descriptor.as_string()}')

wallet = bdk.Wallet(
             descriptor=descriptor,
             change_descriptor=change_descriptor,
             network=network,
             database_config=bdk.DatabaseConfig.MEMORY(),
         )

wallet.sync(blockchain, None)
balance = wallet.get_balance()
print(f"Wallet balance is: {balance.total}")

for tx in wallet.list_transactions(True):
    print(tx.confirmation_time.height,  datetime.datetime.fromtimestamp(tx.confirmation_time.timestamp).strftime("%Y-%m-%d %H:%M") , tx.txid)

this gives

image

where the marked row has the time: 2023-01-18 06:25

However it should be 2023-09-24 13:57

The row above the marked one has however the correct timestamp.

the error is not determinstic, with repeated executions I get different timestamps

image image image

and it appears that the timestamps are mixed up between the different txs image

Originally opened in fulcrum: https://github.com/cculianu/Fulcrum/issues/233

cculianu commented 9 months ago

Potential theory: The client is assuming batch results come back in the exact order of the batch request. As per the JSON-RPC spec, this is not necessarily what all servers do:

The Response objects being returned from a batch call MAY be returned in any order within the Array. The Client SHOULD match contexts between the set of Request objects and the resulting set of Response objects based on the id member within each Object.

As such, Fulcrum returns batch results in an unspecified order (internally the results array is built as the results come in from an asynchronous task, which is why they are in a jumbled order).

Likely the client is assuming the batch results are in the same order as the batch request? Maybe?

andreasgriffin commented 2 months ago

with the 1.0 beta2 I cannot reproduce the bug. I assume it is solved.

import bdkpython as bdk 
import datetime

network = bdk.bitcoin.Network.BITCOIN
electrum = bdk.bdk.ElectrumClient("my-server:50001")

mnemonic = bdk.Mnemonic.from_string('bacon '*24)
print(mnemonic )

descriptor = bdk.Descriptor.new_bip84(
            secret_key=bdk.DescriptorSecretKey(network, mnemonic, ''),
             keychain=bdk.KeychainKind.EXTERNAL,
             network=network,
)

change_descriptor = bdk.Descriptor.new_bip84(
            secret_key=bdk.DescriptorSecretKey(network, mnemonic, ''),
             keychain=bdk.KeychainKind.INTERNAL,
             network=network,
)

print(f'xpub {descriptor }')

wallet = bdk.Wallet(
             descriptor=descriptor,
             change_descriptor=change_descriptor,
             network=network, 
             connection=bdk.Connection.new_in_memory()
         )

sync_request_builder =wallet.start_full_scan()
sync_update = electrum.full_scan(sync_request_builder.build(), stop_gap=10, batch_size=1, fetch_prev_txouts=True)

wallet.apply_update(sync_update)
balance = wallet.balance()
print(f"Wallet balance is: {balance.total.to_sat()}")

for tx in wallet.transactions():

    print(tx.chain_position.confirmation_block_time.block_id.height,  tx.chain_position.confirmation_block_time.confirmation_time,
          datetime.datetime.fromtimestamp(tx.chain_position.confirmation_block_time.confirmation_time).strftime("%Y-%m-%d %H:%M"),
         tx.transaction.compute_txid())