Пример #1
0
def test_process_pending_ledger_update_missing_last_transfer(account, max_count, current_ts):
    def get_ledger_update_entries_count():
        p.process_pending_log_entries(C_ID)
        return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all())

    creation_date = date(2020, 1, 2)
    assert get_ledger_update_entries_count() == 0

    p.process_account_update_signal(
        debtor_id=D_ID,
        creditor_id=C_ID,
        creation_date=creation_date,
        last_change_ts=current_ts,
        last_change_seqnum=1,
        principal=1000,
        interest=0.0,
        interest_rate=0.0,
        last_interest_rate_change_ts=models.TS0,
        transfer_note_max_bytes=500,
        last_config_ts=current_ts,
        last_config_seqnum=0,
        negligible_amount=10.0,
        config_flags=models.DEFAULT_CONFIG_FLAGS,
        config_data='',
        account_id=str(C_ID),
        debtor_info_iri=None,
        debtor_info_content_type=None,
        debtor_info_sha256=None,
        last_transfer_number=3,
        last_transfer_committed_at=current_ts - timedelta(days=20),
        ts=current_ts,
        ttl=10000,
    )

    assert len(PendingLedgerUpdate.query.all()) == 1
    max_delay = timedelta(days=30)
    while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=max_delay):
        pass
    assert len(PendingLedgerUpdate.query.all()) == 0
    lue_count = get_ledger_update_entries_count()
    assert lue_count == 0
    data = p.get_account_ledger(C_ID, D_ID)
    assert data.ledger_principal == 0
    assert data.ledger_last_entry_id == 0
    assert data.ledger_last_transfer_number == 0
    assert data.ledger_latest_update_id == 1
    assert len(PendingLedgerUpdate.query.all()) == 0

    max_delay = timedelta(days=10)
    p.ensure_pending_ledger_update(C_ID, D_ID)
    assert len(PendingLedgerUpdate.query.all()) == 1
    while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=max_delay):
        pass
    lue_count = get_ledger_update_entries_count()
    assert lue_count == 1
    data = p.get_account_ledger(C_ID, D_ID)
    assert data.ledger_principal == 1000
    assert data.ledger_last_entry_id == 1
    assert data.ledger_last_transfer_number == 3
    assert data.ledger_latest_update_id == 2
Пример #2
0
def test_delete_account(db_session, account, current_ts):
    with pytest.raises(p.AccountDoesNotExist):
        p.delete_account(C_ID, 1234)

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'last_change_ts': current_ts,
        'last_change_seqnum': 1,
        'principal': 1000,
        'interest': 0.0,
        'interest_rate': 5.0,
        'last_interest_rate_change_ts': current_ts,
        'last_transfer_number': 1,
        'last_transfer_committed_at': current_ts,
        'last_config_ts': current_ts,
        'last_config_seqnum': 1,
        'creation_date': date(2020, 1, 15),
        'negligible_amount': 0.0,
        'ts': current_ts,
        'ttl': 1000000,
        'account_id': str(C_ID),
        'config_data': '',
        'config_flags': 0,
        'debtor_info_iri': 'http://example.com',
        'debtor_info_content_type': None,
        'debtor_info_sha256': None,
        'transfer_note_max_bytes': 500,
    }

    p.process_account_update_signal(**params)
    with pytest.raises(p.UnsafeAccountDeletion):
        p.delete_account(C_ID, D_ID)

    p.update_account_config(
        C_ID, D_ID,
        is_scheduled_for_deletion=True, negligible_amount=0.0,
        allow_unsafe_deletion=False, latest_update_id=2)

    config = p.get_account_config(C_ID, D_ID)
    params['last_change_seqnum'] += 1
    params['last_config_ts'] = config.last_config_ts
    params['last_config_seqnum'] = config.last_config_seqnum
    params['negligible_amount'] = config.negligible_amount
    params['config_flags'] = config.config_flags
    p.process_account_update_signal(**params)
    p.process_account_purge_signal(debtor_id=D_ID, creditor_id=C_ID, creation_date=date(2020, 1, 15))

    p.delete_account(C_ID, D_ID)
    assert not p.get_account(C_ID, D_ID)
Пример #3
0
def test_process_pending_ledger_update(account, max_count, current_ts):
    def get_ledger_update_entries_count():
        p.process_pending_log_entries(C_ID)
        return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all())

    creation_date = date(2020, 1, 2)

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': creation_date,
        'transfer_number': 1,
        'coordinator_type': 'direct',
        'sender': '666',
        'recipient': str(C_ID),
        'acquired_amount': 1000,
        'transfer_note_format': 'json',
        'transfer_note': '{"message": "test"}',
        'committed_at': current_ts,
        'principal': 1100,
        'ts': current_ts,
        'previous_transfer_number': 0,
        'retention_interval': timedelta(days=5),
    }
    p.process_account_transfer_signal(**params)

    params['transfer_number'] = 20
    params['previous_transfer_number'] = 1
    params['principal'] = 2100
    p.process_account_transfer_signal(**params)

    params['transfer_number'] = 22
    params['previous_transfer_number'] = 21
    params['principal'] = 4150
    p.process_account_transfer_signal(**params)

    assert get_ledger_update_entries_count() == 0
    assert p.get_pending_ledger_updates() == []

    p.process_account_update_signal(
        debtor_id=D_ID,
        creditor_id=C_ID,
        creation_date=creation_date,
        last_change_ts=current_ts,
        last_change_seqnum=1,
        principal=0,
        interest=0.0,
        interest_rate=0.0,
        last_interest_rate_change_ts=models.TS0,
        transfer_note_max_bytes=500,
        last_config_ts=current_ts,
        last_config_seqnum=0,
        negligible_amount=10.0,
        config_flags=models.DEFAULT_CONFIG_FLAGS,
        config_data='',
        account_id=str(C_ID),
        debtor_info_iri='http://example.com',
        debtor_info_content_type=None,
        debtor_info_sha256=None,
        last_transfer_number=0,
        last_transfer_committed_at=models.TS0,
        ts=current_ts,
        ttl=10000,
    )
    assert get_ledger_update_entries_count() == 0
    assert p.get_pending_ledger_updates() == [(C_ID, D_ID)]
    assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 0

    assert p.process_pending_ledger_update(2222, D_ID, max_count=max_count, max_delay=timedelta(days=10000))
    assert p.process_pending_ledger_update(C_ID, 1111, max_count=max_count, max_delay=timedelta(days=10000))

    n = 0
    while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=timedelta(days=10000)):
        x = len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000))
        assert x > n
        n = x

    lue_count = get_ledger_update_entries_count()
    assert lue_count > 0
    assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 3
    assert p.get_pending_ledger_updates() == []

    params['transfer_number'] = 21
    params['previous_transfer_number'] = 20
    params['principal'] = 3150
    p.process_account_transfer_signal(**params)

    assert p.get_pending_ledger_updates() == [(C_ID, D_ID)]
    while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=timedelta(days=10000)):
        pass
    assert get_ledger_update_entries_count() > lue_count
    assert p.get_pending_ledger_updates() == []
    assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 6

    log_entry = p.get_log_entries(C_ID, count=1000)[0][-1]
    assert log_entry.creditor_id == C_ID
    assert log_entry.object_type_hint == LogEntry.OTH_ACCOUNT_LEDGER
    assert log_entry.debtor_id == D_ID
    assert log_entry.object_update_id > 2
    assert not log_entry.is_deleted
Пример #4
0
def test_process_account_transfer_signal(db_session, account, current_ts):
    def get_committed_tranfer_entries_count():
        p.process_pending_log_entries(C_ID)
        return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_COMMITTED_TRANSFER).all())

    def has_pending_ledger_update():
        return len(PendingLedgerUpdate.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).all()) > 0

    def delete_pending_ledger_update():
        PendingLedgerUpdate.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).delete()
        db_session.commit()

    assert not has_pending_ledger_update()
    p.process_account_update_signal(
        debtor_id=D_ID,
        creditor_id=C_ID,
        creation_date=date(2020, 1, 2),
        last_change_ts=current_ts,
        last_change_seqnum=1,
        principal=1000,
        interest=12.0,
        interest_rate=5.0,
        last_interest_rate_change_ts=current_ts - timedelta(days=1),
        transfer_note_max_bytes=500,
        last_config_ts=current_ts,
        last_config_seqnum=0,
        negligible_amount=100.0,
        config_flags=models.DEFAULT_CONFIG_FLAGS,
        config_data='',
        account_id=str(C_ID),
        debtor_info_iri='http://example.com',
        debtor_info_content_type=None,
        debtor_info_sha256=None,
        last_transfer_number=123,
        last_transfer_committed_at=current_ts - timedelta(days=2),
        ts=current_ts,
        ttl=10000,
    )
    assert has_pending_ledger_update()
    delete_pending_ledger_update()

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': date(2020, 1, 2),
        'transfer_number': 1,
        'coordinator_type': 'direct',
        'sender': '666',
        'recipient': str(C_ID),
        'acquired_amount': 100,
        'transfer_note_format': 'json',
        'transfer_note': '{"message": "test"}',
        'committed_at': current_ts,
        'principal': 1000,
        'ts': current_ts - timedelta(days=6),
        'previous_transfer_number': 0,
        'retention_interval': timedelta(days=5),
    }
    p.process_account_transfer_signal(**params)
    assert len(CommittedTransfer.query.all()) == 0
    assert get_committed_tranfer_entries_count() == 0
    assert not has_pending_ledger_update()

    params['retention_interval'] = timedelta(days=7)
    p.process_account_transfer_signal(**params)
    ct = CommittedTransfer.query.one()
    assert ct.debtor_id == D_ID
    assert ct.creditor_id == C_ID
    assert ct.creation_date == params['creation_date']
    assert ct.transfer_number == 1
    assert ct.coordinator_type == 'direct'
    assert ct.sender == '666'
    assert ct.recipient == str(C_ID)
    assert ct.acquired_amount == 100
    assert ct.transfer_note_format == params['transfer_note_format']
    assert ct.transfer_note == params['transfer_note']
    assert ct.committed_at == current_ts
    assert ct.principal == 1000
    assert ct.previous_transfer_number == 0
    assert get_committed_tranfer_entries_count() == 1
    assert has_pending_ledger_update()
    delete_pending_ledger_update()

    params['retention_interval'] = timedelta(days=7)
    p.process_account_transfer_signal(**params)
    assert len(CommittedTransfer.query.all()) == 1
    assert get_committed_tranfer_entries_count() == 1
    le = LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_COMMITTED_TRANSFER).one()
    assert le.creditor_id == C_ID
    assert le.debtor_id == D_ID
    assert le.creation_date == params['creation_date']
    assert le.transfer_number == 1
    assert not le.is_deleted
    assert le.object_update_id is None
    assert not has_pending_ledger_update()

    params['creditor_id'] = 1235
    p.process_account_transfer_signal(**params)
    assert len(CommittedTransfer.query.all()) == 1
    assert get_committed_tranfer_entries_count() == 1
Пример #5
0
def test_update_account_config(account, current_ts):
    def get_data():
        return AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).one()

    def get_info_entries_count():
        p.process_pending_log_entries(C_ID)
        return len(LogEntry.query.filter_by(object_type='AccountInfo').all())

    creation_date = current_ts.date()
    data = get_data()
    assert not data.is_config_effectual
    assert not data.is_deletion_safe
    assert not data.has_server_account
    assert get_info_entries_count() == 0

    p.update_account_config(C_ID, D_ID,
                            is_scheduled_for_deletion=True,
                            negligible_amount=1e30,
                            allow_unsafe_deletion=False,
                            latest_update_id=2)

    data = get_data()
    assert not data.is_config_effectual
    assert not data.is_deletion_safe
    assert not data.has_server_account
    assert get_info_entries_count() == 0

    data = get_data()
    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': creation_date,
        'last_change_ts': current_ts,
        'last_change_seqnum': 1,
        'principal': 0,
        'interest': 0.0,
        'interest_rate': 0.0,
        'last_interest_rate_change_ts': models.TS0,
        'transfer_note_max_bytes': 500,
        'last_config_ts': data.last_config_ts,
        'last_config_seqnum': data.last_config_seqnum,
        'negligible_amount': data.negligible_amount,
        'config_flags': data.config_flags,
        'config_data': '',
        'account_id': str(C_ID),
        'debtor_info_iri': 'http://example.com',
        'debtor_info_content_type': None,
        'debtor_info_sha256': None,
        'last_transfer_number': 0,
        'last_transfer_committed_at': models.TS0,
        'ts': current_ts,
        'ttl': 10000,
    }
    p.process_account_update_signal(**params)
    data = get_data()
    assert data.is_config_effectual
    assert not data.is_deletion_safe
    assert data.has_server_account
    assert get_info_entries_count() == 1

    p.process_account_purge_signal(debtor_id=D_ID, creditor_id=C_ID, creation_date=creation_date)
    data = get_data()
    assert data.is_config_effectual
    assert data.is_deletion_safe
    assert not data.has_server_account
    assert get_info_entries_count() == 2

    p.update_account_config(C_ID, D_ID,
                            is_scheduled_for_deletion=True,
                            negligible_amount=1e30,
                            allow_unsafe_deletion=False,
                            latest_update_id=3)
    data = get_data()
    assert not data.is_config_effectual
    assert not data.is_deletion_safe
    assert not data.has_server_account
    assert get_info_entries_count() == 3
Пример #6
0
def test_process_account_update_signal(db_session, account):
    AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).update({
        'ledger_principal': 1001,
        'ledger_last_entry_id': 88,
        'ledger_last_transfer_number': 888,
    })

    def get_data():
        return AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).one()

    ad = get_data()
    assert not ad.is_config_effectual
    assert ad.ledger_principal == 1001
    assert ad.ledger_last_entry_id == 88
    assert ad.negligible_amount == models.DEFAULT_NEGLIGIBLE_AMOUNT
    assert ad.config_flags == models.DEFAULT_CONFIG_FLAGS
    assert ad.last_change_seqnum == 0
    assert ad.config_error is None
    last_ts = ad.last_config_ts
    last_seqnum = ad.last_config_seqnum
    negligible_amount = ad.negligible_amount
    config_flags = ad.config_flags
    last_heartbeat_ts = ad.last_heartbeat_ts
    creation_date = date(2020, 1, 15)

    time.sleep(0.1)
    current_ts = datetime.now(tz=timezone.utc)
    assert last_heartbeat_ts < current_ts

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': creation_date,
        'last_change_ts': current_ts,
        'last_change_seqnum': 1,
        'principal': 1000,
        'interest': 12.0,
        'interest_rate': 5.0,
        'last_interest_rate_change_ts': current_ts - timedelta(days=1),
        'transfer_note_max_bytes': 500,
        'last_config_ts': last_ts,
        'last_config_seqnum': last_seqnum,
        'negligible_amount': negligible_amount,
        'config_flags': config_flags,
        'config_data': '',
        'account_id': str(C_ID),
        'debtor_info_iri': 'http://example.com',
        'debtor_info_content_type': 'text/plain',
        'debtor_info_sha256': 32 * b'\xff',
        'last_transfer_number': 22,
        'last_transfer_committed_at': current_ts - timedelta(days=2),
        'ts': current_ts,
        'ttl': 0,
    }

    p.process_account_update_signal(**params)
    ad = get_data()
    assert last_heartbeat_ts == ad.last_heartbeat_ts
    assert ad.last_change_seqnum == 0
    assert not ad.is_config_effectual
    assert ad.config_error is None

    params['ttl'] = 10000
    p.process_account_update_signal(**params)
    ad = get_data()
    assert ad.last_heartbeat_ts > last_heartbeat_ts
    assert ad.is_config_effectual
    assert ad.creation_date == creation_date
    assert ad.last_change_ts == current_ts
    assert ad.last_change_seqnum == 1
    assert ad.principal == 1000
    assert ad.interest == 12.0
    assert ad.interest_rate == 5.0
    assert ad.last_interest_rate_change_ts == current_ts - timedelta(days=1)
    assert ad.transfer_note_max_bytes == 500
    assert ad.last_config_ts == last_ts
    assert ad.last_config_seqnum == last_seqnum
    assert ad.account_id == str(C_ID)
    assert ad.debtor_info_iri == 'http://example.com'
    assert ad.debtor_info_content_type == 'text/plain'
    assert ad.debtor_info_sha256 == 32 * b'\xff'
    assert ad.last_transfer_number == 22
    assert ad.last_transfer_committed_at == current_ts - timedelta(days=2)
    assert ad.config_error is None

    p.process_account_update_signal(**params)
    assert ad.last_change_seqnum == 1

    p.process_rejected_config_signal(
        debtor_id=D_ID,
        creditor_id=C_ID,
        config_ts=last_ts,
        config_seqnum=last_seqnum,
        negligible_amount=negligible_amount,
        config_data='',
        config_flags=config_flags,
        rejection_code='TEST_CONFIG_ERROR',
    )
    ad = get_data()
    assert ad.config_error == 'TEST_CONFIG_ERROR'

    params['last_change_seqnum'] = 2
    params['principal'] = 1100
    params['negligible_amount'] = 3.33
    params['config_flags'] = 77
    p.process_account_update_signal(**params)
    ad = get_data()
    assert ad.last_change_seqnum == 2
    assert ad.principal == 1100
    assert ad.negligible_amount == negligible_amount
    assert ad.config_flags == config_flags
    assert not ad.is_config_effectual
    assert ad.config_error == 'TEST_CONFIG_ERROR'

    params['last_change_seqnum'] = 3
    params['negligible_amount'] = negligible_amount
    params['config_flags'] = config_flags
    p.process_account_update_signal(**params)
    ad = get_data()
    assert ad.last_change_seqnum == 3
    assert ad.is_config_effectual
    assert ad.config_error is None

    params['creation_date'] = creation_date + timedelta(days=2)
    p.process_account_update_signal(**params)
    ad = get_data()
    assert ad.last_change_seqnum == 3
    assert ad.is_config_effectual
    assert ad.creation_date == creation_date + timedelta(days=2)
    assert ad.config_error is None
    assert ad.ledger_principal == 0
    assert ad.ledger_last_entry_id == 89
    assert ad.ledger_last_transfer_number == 0

    # Discard orphaned account.
    params['debtor_id'] = 1235
    params['last_change_seqnum'] = 1
    params['negligible_amount'] = 2.0
    p.process_account_update_signal(**params)
    cas = ConfigureAccountSignal.query.filter_by(creditor_id=C_ID, debtor_id=1235).one()
    assert cas.negligible_amount > 1e22
    assert cas.config_flags & AccountData.CONFIG_SCHEDULED_FOR_DELETION_FLAG

    params['last_change_seqnum'] = 2
    params['negligible_amount'] = models.DEFAULT_NEGLIGIBLE_AMOUNT
    params['config_flags'] = AccountData.CONFIG_SCHEDULED_FOR_DELETION_FLAG
    p.process_account_update_signal(**params)
    assert ConfigureAccountSignal.query.filter_by(creditor_id=C_ID, debtor_id=1235).one()

    assert list(p.get_creditors_with_pending_log_entries()) == [(C_ID,)]
    p.process_pending_log_entries(1235)
    p.process_pending_log_entries(C_ID)
    assert len(models.LogEntry.query.filter_by(object_type='AccountInfo').all()) == 3
    assert len(models.LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all()) == 1
Пример #7
0
def test_process_ledger_entries(app, db_session, current_ts):
    _create_new_creditor(C_ID, activate=True)
    p.create_new_account(C_ID, D_ID)

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': date(2020, 1, 1),
        'last_change_ts': current_ts,
        'last_change_seqnum': 1,
        'principal': 1000,
        'interest': 0.0,
        'interest_rate': 5.0,
        'last_interest_rate_change_ts': current_ts,
        'transfer_note_max_bytes': 500,
        'last_config_ts': current_ts,
        'last_config_seqnum': 1,
        'negligible_amount': 0.0,
        'config_flags': 0,
        'config_data': '',
        'account_id': str(C_ID),
        'debtor_info_iri': 'http://example.com',
        'debtor_info_content_type': None,
        'debtor_info_sha256': None,
        'last_transfer_number': 0,
        'last_transfer_committed_at': current_ts,
        'ts': current_ts,
        'ttl': 100000,
    }
    p.process_account_update_signal(**params)

    params = {
        'debtor_id': D_ID,
        'creditor_id': C_ID,
        'creation_date': date(2020, 1, 1),
        'transfer_number': 1,
        'coordinator_type': 'direct',
        'sender': '666',
        'recipient': str(C_ID),
        'acquired_amount': 200,
        'transfer_note_format': 'json',
        'transfer_note': '{"message": "test"}',
        'committed_at': current_ts,
        'principal': 200,
        'ts': current_ts,
        'previous_transfer_number': 0,
        'retention_interval': timedelta(days=5),
    }
    p.process_account_transfer_signal(**params)
    params['transfer_number'] = 2
    params['principal'] = 400
    params['previous_transfer_number'] = 1
    p.process_account_transfer_signal(**params)

    assert len(
        p.get_account_ledger_entries(C_ID, D_ID, prev=10000, count=10000)) == 0
    runner = app.test_cli_runner()
    result = runner.invoke(args=[
        'swpt_creditors', 'process_ledger_updates', '--burst=1',
        '--quit-early', '--wait=0'
    ])
    assert not result.output
    assert len(
        p.get_account_ledger_entries(C_ID, D_ID, prev=10000, count=10000)) == 2
Пример #8
0
def on_account_update_signal(
        debtor_id: int,
        creditor_id: int,
        last_change_ts: str,
        last_change_seqnum: int,
        principal: int,
        interest: float,
        interest_rate: float,
        demurrage_rate: float,
        commit_period: int,
        transfer_note_max_bytes: int,
        last_interest_rate_change_ts: str,
        last_transfer_number: int,
        last_transfer_committed_at: str,
        last_config_ts: str,
        last_config_seqnum: int,
        creation_date: str,
        negligible_amount: float,
        config_data: str,
        config_flags: int,
        ts: str,
        ttl: int,
        account_id: str,
        debtor_info_iri: str,
        debtor_info_content_type: str,
        debtor_info_sha256: str,
        *args, **kwargs) -> None:

    assert 0 <= transfer_note_max_bytes <= TRANSFER_NOTE_MAX_BYTES
    assert len(config_data) <= CONFIG_DATA_MAX_BYTES and len(config_data.encode('utf8')) <= CONFIG_DATA_MAX_BYTES
    assert account_id == '' or len(account_id) <= 100 and account_id.encode('ascii')
    assert len(debtor_info_iri) <= 200
    assert debtor_info_content_type == '' or (
        len(debtor_info_content_type) <= 100 and debtor_info_content_type.encode('ascii'))
    assert debtor_info_sha256 == '' or len(debtor_info_sha256) == 64

    procedures.process_account_update_signal(
        debtor_id=debtor_id,
        creditor_id=creditor_id,
        creation_date=date.fromisoformat(creation_date),
        last_change_ts=datetime.fromisoformat(last_change_ts),
        last_change_seqnum=last_change_seqnum,
        principal=principal,
        interest=interest,
        interest_rate=interest_rate,
        last_interest_rate_change_ts=datetime.fromisoformat(last_interest_rate_change_ts),
        transfer_note_max_bytes=transfer_note_max_bytes,
        last_config_ts=datetime.fromisoformat(last_config_ts),
        last_config_seqnum=last_config_seqnum,
        negligible_amount=negligible_amount,
        config_flags=config_flags,
        config_data=config_data,
        account_id=account_id,
        debtor_info_iri=debtor_info_iri or None,
        debtor_info_content_type=debtor_info_content_type or None,
        debtor_info_sha256=b16decode(debtor_info_sha256, casefold=True) or None,
        last_transfer_number=last_transfer_number,
        last_transfer_committed_at=datetime.fromisoformat(last_transfer_committed_at),
        ts=datetime.fromisoformat(ts),
        ttl=ttl,
    )