From 7dc9f52fe0f345e068b736f13a4097f5af2ea8c7 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 14 Nov 2016 17:18:27 +0100 Subject: [PATCH] remove transaction timestamp --- bigchaindb/common/schema/transaction.yaml | 7 -- bigchaindb/common/transaction.py | 15 +-- docs/root/source/data-models/block-model.rst | 2 +- .../source/data-models/transaction-model.md | 8 +- docs/root/source/index.rst | 1 - docs/root/source/timestamps.md | 95 ------------------- .../http-client-server-api.rst | 3 - docs/server/source/schema/transaction.rst | 9 -- tests/common/test_transaction.py | 14 +-- tests/db/test_bigchain_api.py | 2 +- 10 files changed, 12 insertions(+), 144 deletions(-) delete mode 100644 docs/root/source/timestamps.md diff --git a/bigchaindb/common/schema/transaction.yaml b/bigchaindb/common/schema/transaction.yaml index a7e4117e..f1864cc3 100644 --- a/bigchaindb/common/schema/transaction.yaml +++ b/bigchaindb/common/schema/transaction.yaml @@ -28,7 +28,6 @@ properties: - fulfillments - conditions - operation - - timestamp - metadata - asset properties: @@ -64,8 +63,6 @@ properties: contain an id and an object with freeform metadata. See: `Metadata`_. - timestamp: - "$ref": "#/definitions/timestamp" version: type: integer minimum: 1 @@ -262,7 +259,3 @@ definitions: User provided transaction metadata. additionalProperties: true - type: 'null' - timestamp: - type: string - description: | - User provided timestamp of the transaction. diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index b8fc14d0..6a7ec629 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -648,7 +648,6 @@ class Transaction(object): transaction.Condition`, optional): Define the assets to lock. metadata (:class:`~bigchaindb.common.transaction.Metadata`): Metadata to be stored along with the Transaction. - timestamp (int): Defines the time a Transaction was created. version (int): Defines the version number of a Transaction. """ CREATE = 'CREATE' @@ -658,11 +657,11 @@ class Transaction(object): VERSION = 1 def __init__(self, operation, asset, fulfillments=None, conditions=None, - metadata=None, timestamp=None, version=None): + metadata=None, version=None): """The constructor allows to create a customizable Transaction. Note: - When no `version` or `timestamp`, is provided, one is being + When no `version` is provided, one is being generated by this method. Args: @@ -677,7 +676,6 @@ class Transaction(object): lock. metadata (:class:`~bigchaindb.common.transaction.Metadata`): Metadata to be stored along with the Transaction. - timestamp (int): Defines the time a Transaction was created. version (int): Defines the version number of a Transaction. """ @@ -701,7 +699,6 @@ class Transaction(object): raise TypeError('`metadata` must be a Metadata instance or None') self.version = version if version is not None else self.VERSION - self.timestamp = timestamp if timestamp else gen_timestamp() self.operation = operation self.asset = asset if asset else Asset() self.conditions = conditions if conditions else [] @@ -941,7 +938,7 @@ class Transaction(object): # previously signed ones. tx_partial = Transaction(self.operation, self.asset, [fulfillment], self.conditions, self.metadata, - self.timestamp, self.version) + self.version) tx_partial_dict = tx_partial.to_dict() tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) @@ -1103,8 +1100,7 @@ class Transaction(object): Transactions. """ tx = Transaction(self.operation, self.asset, [fulfillment], - self.conditions, self.metadata, self.timestamp, - self.version) + self.conditions, self.metadata, self.version) tx_dict = tx.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) tx_serialized = Transaction._to_str(tx_dict) @@ -1188,7 +1184,6 @@ class Transaction(object): 'conditions': [condition.to_dict(cid) for cid, condition in enumerate(self.conditions)], 'operation': str(self.operation), - 'timestamp': self.timestamp, 'metadata': metadata, 'asset': asset, } @@ -1291,4 +1286,4 @@ class Transaction(object): asset = AssetLink.from_dict(tx['asset']) return cls(tx['operation'], asset, fulfillments, conditions, - metadata, tx['timestamp'], tx_body['version']) + metadata, tx_body['version']) diff --git a/docs/root/source/data-models/block-model.rst b/docs/root/source/data-models/block-model.rst index 94808426..e2e3b418 100644 --- a/docs/root/source/data-models/block-model.rst +++ b/docs/root/source/data-models/block-model.rst @@ -20,7 +20,7 @@ A block has the following structure: - ``id``: The hash of the serialized ``block`` (i.e. the ``timestamp``, ``transactions``, ``node_pubkey``, and ``voters``). This is also a database primary key; that's how we ensure that all blocks are unique. - ``block``: - - ``timestamp``: The Unix time when the block was created. It's provided by the node that created the block. See `the page about timestamps `_. + - ``timestamp``: The Unix time when the block was created. It's provided by the node that created the block. - ``transactions``: A list of the transactions included in the block. - ``node_pubkey``: The public key of the node that created the block. - ``voters``: A list of the public keys of federation nodes at the time the block was created. diff --git a/docs/root/source/data-models/transaction-model.md b/docs/root/source/data-models/transaction-model.md index f8cb5929..f9503093 100644 --- a/docs/root/source/data-models/transaction-model.md +++ b/docs/root/source/data-models/transaction-model.md @@ -10,7 +10,6 @@ A transaction has the following structure: "fulfillments": [""], "conditions": [""], "operation": "", - "timestamp": "", "asset": "", "metadata": { "id": "", @@ -22,7 +21,7 @@ A transaction has the following structure: Here's some explanation of the contents of a transaction: -- `id`: The hash of everything inside the serialized `transaction` body (i.e. `fulfillments`, `conditions`, `operation`, `timestamp` and `data`; see below), with one wrinkle: for each fulfillment in `fulfillments`, `fulfillment` is set to `null`. The `id` is also the database primary key. +- `id`: The hash of everything inside the serialized `transaction` body (i.e. `fulfillments`, `conditions`, `operation`, and `data`; see below), with one wrinkle: for each fulfillment in `fulfillments`, `fulfillment` is set to `null`. The `id` is also the database primary key. - `version`: Version number of the transaction model, so that software can support different transaction models. - `transaction`: - `fulfillments`: List of fulfillments. Each _fulfillment_ contains a pointer to an unspent asset @@ -32,7 +31,6 @@ Here's some explanation of the contents of a transaction: - `conditions`: List of conditions. Each _condition_ is a _crypto-condition_ that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners. See the page about [Crypto-Conditions and Fulfillments](crypto-conditions.html). - `operation`: String representation of the operation being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated. - - `timestamp`: The Unix time when the transaction was created. It's provided by the client. See the page about [timestamps in BigchainDB](../timestamps.html). - `asset`: Definition of the digital asset. See next section. - `metadata`: - `id`: UUID version 4 (random) converted to a string of hex digits in standard form. @@ -40,6 +38,6 @@ Here's some explanation of the contents of a transaction: Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the `fulfillment` string of each fulfillment. A creation transaction is signed by the node that created it. A transfer transaction is signed by whoever currently controls or owns it. -What gets signed? For each fulfillment in the transaction, the "fullfillment message" that gets signed includes the `operation`, `timestamp`, `data`, `version`, `id`, corresponding `condition`, and the fulfillment itself, except with its fulfillment string set to `null`. The computed signature goes into creating the `fulfillment` string of the fulfillment. +What gets signed? For each fulfillment in the transaction, the "fullfillment message" that gets signed includes the `operation`, `data`, `version`, `id`, corresponding `condition`, and the fulfillment itself, except with its fulfillment string set to `null`. The computed signature goes into creating the `fulfillment` string of the fulfillment. -One other note: Currently, transactions contain only the public keys of asset-owners (i.e. who own an asset or who owned an asset in the past), inside the conditions and fulfillments. A transaction does _not_ contain the public key of the client (computer) which generated and sent it to a BigchainDB node. In fact, there's no need for a client to _have_ a public/private keypair. In the future, each client may also have a keypair, and it may have to sign each sent transaction (using its private key); see [Issue #347 on GitHub](https://github.com/bigchaindb/bigchaindb/issues/347). In practice, a person might think of their keypair as being both their "ownership-keypair" and their "client-keypair," but there is a difference, just like there's a difference between Joe and Joe's computer. \ No newline at end of file +One other note: Currently, transactions contain only the public keys of asset-owners (i.e. who own an asset or who owned an asset in the past), inside the conditions and fulfillments. A transaction does _not_ contain the public key of the client (computer) which generated and sent it to a BigchainDB node. In fact, there's no need for a client to _have_ a public/private keypair. In the future, each client may also have a keypair, and it may have to sign each sent transaction (using its private key); see [Issue #347 on GitHub](https://github.com/bigchaindb/bigchaindb/issues/347). In practice, a person might think of their keypair as being both their "ownership-keypair" and their "client-keypair," but there is a difference, just like there's a difference between Joe and Joe's computer. diff --git a/docs/root/source/index.rst b/docs/root/source/index.rst index c8ddb1ff..2235edc4 100644 --- a/docs/root/source/index.rst +++ b/docs/root/source/index.rst @@ -85,5 +85,4 @@ More About BigchainDB assets smart-contracts transaction-concepts - timestamps data-models/index diff --git a/docs/root/source/timestamps.md b/docs/root/source/timestamps.md deleted file mode 100644 index 532586a6..00000000 --- a/docs/root/source/timestamps.md +++ /dev/null @@ -1,95 +0,0 @@ -# Timestamps in BigchainDB - -Each transaction, block and vote has an associated timestamp. Interpreting those timestamps is tricky, hence the need for this section. - - -## Timestamp Sources & Accuracy - -A transaction's timestamp is provided by the client which created and submitted the transaction to a BigchainDB node. A block's timestamp is provided by the BigchainDB node which created the block. A vote's timestamp is provided by the BigchainDB node which created the vote. - -When a BigchainDB client or node needs a timestamp, it calls a BigchainDB utility function named `timestamp()`. There's a detailed explanation of how that function works below, but the short version is that it gets the [Unix time](https://en.wikipedia.org/wiki/Unix_time) from its system clock, rounded to the nearest second. - -We can't say anything about the accuracy of the system clock on clients. Timestamps from clients are still potentially useful, however, in a statistical sense. We say more about that below. - -We advise BigchainDB nodes to run special software (an "NTP daemon") to keep their system clock in sync with standard time servers. (NTP stands for [Network Time Protocol](https://en.wikipedia.org/wiki/Network_Time_Protocol).) - - -## Converting Timestamps to UTC - -To convert a BigchainDB timestamp (a Unix time) to UTC, you need to know how the node providing the timestamp was set up. That's because different setups will report a different "Unix time" value around leap seconds! There's [a nice Red Hat Developer Blog post about the various setup options](http://developers.redhat.com/blog/2015/06/01/five-different-ways-handle-leap-seconds-ntp/). If you want more details, see [David Mills' pages about leap seconds, NTP, etc.](https://www.eecis.udel.edu/~mills/leap.html) (David Mills designed NTP.) - -We advise BigchainDB nodes to run an NTP daemon with particular settings so that their timestamps are consistent. - -If a timestamp comes from a node that's set up as we advise, it can be converted to UTC as follows: - -1. Use a standard "Unix time to UTC" converter to get a UTC timestamp. -2. Is the UTC timestamp a leap second, or the second before/after a leap second? There's [a list of all the leap seconds on Wikipedia](https://en.wikipedia.org/wiki/Leap_second). -3. If no, then you are done. -4. If yes, then it might not be possible to convert it to a single UTC timestamp. Even if it can't be converted to a single UTC timestamp, it _can_ be converted to a list of two possible UTC timestamps. -Showing how to do that is beyond the scope of this documentation. -In all likelihood, you will never have to worry about leap seconds because they are very rare. -(There were only 26 between 1972 and the end of 2015.) - - -## Calculating Elapsed Time Between Two Timestamps - -There's another gotcha with (Unix time) timestamps: you can't calculate the real-world elapsed time between two timestamps (correctly) by subtracting the smaller timestamp from the larger one. The result won't include any of the leap seconds that occured between the two timestamps. You could look up how many leap seconds happened between the two timestamps and add that to the result. There are many library functions for working with timestamps; those are beyond the scope of this documentation. - - -## Avoid Doing Transactions Around Leap Seconds - -Because of the ambiguity and confusion that arises with Unix time around leap seconds, we advise users to avoid creating transactions around leap seconds. - - -## Interpreting Sets of Timestamps - -You can look at many timestamps to get a statistical sense of when something happened. For example, a transaction in a decided-valid block has many associated timestamps: - -* its own timestamp -* the timestamps of the other transactions in the block; there could be as many as 999 of those -* the timestamp of the block -* the timestamps of all the votes on the block - -Those timestamps come from many sources, so you can look at all of them to get some statistical sense of when the transaction "actually happened." The timestamp of the block should always be after the timestamp of the transaction, and the timestamp of the votes should always be after the timestamp of the block. - - -## How BigchainDB Uses Timestamps - -BigchainDB _doesn't_ use timestamps to determine the order of transactions or blocks. In particular, the order of blocks is determined by RethinkDB's changefeed on the bigchain table. - -BigchainDB does use timestamps for some things. It uses them to determine if a transaction has been waiting in the backlog for too long (i.e. because the node assigned to it hasn't handled it yet). It also uses timestamps to determine the status of timeout conditions (used by escrow). - - -## Including Trusted Timestamps - -If you want to create a transaction payload with a trusted timestamp, you can. - -One way to do that would be to send a payload to a trusted timestamping service. They will send back a timestamp, a signature, and their public key. They should also explain how you can verify the signature. You can then include the original payload, the timestamp, the signature, and the service's public key in your transaction. That way, anyone with the verification instructions can verify that the original payload was signed by the trusted timestamping service. - - -## How the timestamp() Function Works - -BigchainDB has a utility function named `timestamp()` which amounts to: -```python -timestamp() = str(round(time.time())) -``` - -In other words, it calls the `time()` function in Python's `time` module, [rounds](https://docs.python.org/3/library/functions.html#round) that to the nearest integer, and converts the result to a string. - -It rounds the output of `time.time()` to the nearest second because, according to [the Python documentation for `time.time()`](https://docs.python.org/3.4/library/time.html#time.time), "...not all systems provide time with a better precision than 1 second." - -How does `time.time()` work? If you look in the C source code, it calls `floattime()` and `floattime()` calls [clock_gettime()](https://www.cs.rutgers.edu/~pxk/416/notes/c-tutorials/gettime.html), if it's available. -```text -ret = clock_gettime(CLOCK_REALTIME, &tp); -``` - -With `CLOCK_REALTIME` as the first argument, it returns the "Unix time." ("Unix time" is in quotes because its value around leap seconds depends on how the system is set up; see above.) - - -## Why Not Use UTC, TAI or Some Other Time that Has Unambiguous Timestamps for Leap Seconds? - -It would be nice to use UTC or TAI timestamps, but unfortunately there's no commonly-available, standard way to get always-accurate UTC or TAI timestamps from the operating system on typical computers today (i.e. accurate around leap seconds). - -There _are_ commonly-available, standard ways to get the "Unix time," such as clock_gettime() function available in C. That's what we use (indirectly via Python). ("Unix time" is in quotes because its value around leap seconds depends on how the system is set up; see above.) - -The Unix-time-based timestamps we use are only ambiguous circa leap seconds, and those are very rare. Even for those timestamps, the extra uncertainty is only one second, and that's not bad considering that we only report timestamps to a precision of one second in the first place. All other timestamps can be converted to UTC with no ambiguity. diff --git a/docs/server/source/drivers-clients/http-client-server-api.rst b/docs/server/source/drivers-clients/http-client-server-api.rst index 0b73093f..b38852fc 100644 --- a/docs/server/source/drivers-clients/http-client-server-api.rst +++ b/docs/server/source/drivers-clients/http-client-server-api.rst @@ -99,7 +99,6 @@ POST /transactions/ "refillable": false }, "metadata": null, - "timestamp": "1477578978", "fulfillments": [ { "fid": 0, @@ -156,7 +155,6 @@ POST /transactions/ } ], "operation": "CREATE", - "timestamp": "1477578978", "asset": { "updatable": false, "refillable": false, @@ -265,7 +263,6 @@ GET /transactions/{tx_id} "refillable": false }, "metadata": null, - "timestamp": "1477578978", "fulfillments": [ { "fid": 0, diff --git a/docs/server/source/schema/transaction.rst b/docs/server/source/schema/transaction.rst index df82baf0..68abab0c 100644 --- a/docs/server/source/schema/transaction.rst +++ b/docs/server/source/schema/transaction.rst @@ -142,15 +142,6 @@ See: `Metadata`_. -Transaction.timestamp -^^^^^^^^^^^^^^^^^^^^^ - -**type:** string - -User provided timestamp of the transaction. - - - Condition diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index 4ad93768..e6a4ac5f 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -297,7 +297,6 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id): from .util import validate_transaction_model tx_id = 'l0l' - timestamp = '66666666666' expected = { 'id': tx_id, @@ -308,7 +307,6 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id): 'fulfillments': [user_ffill.to_dict(0)], 'conditions': [user_cond.to_dict(0)], 'operation': Transaction.CREATE, - 'timestamp': timestamp, 'metadata': None, 'asset': { 'id': data_id, @@ -325,7 +323,6 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id): tx_dict = tx.to_dict() tx_dict['id'] = tx_id tx_dict['transaction']['asset']['id'] = data_id - tx_dict['transaction']['timestamp'] = timestamp assert tx_dict == expected @@ -338,11 +335,10 @@ def test_transaction_deserialization(user_ffill, user_cond, data, uuid4): from bigchaindb.common.transaction import Transaction, Asset from .util import validate_transaction_model - timestamp = '66666666666' expected_asset = Asset(data, uuid4) expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill], - [user_cond], None, timestamp, Transaction.VERSION) + [user_cond], None, Transaction.VERSION) tx = { 'version': Transaction.VERSION, @@ -352,7 +348,6 @@ def test_transaction_deserialization(user_ffill, user_cond, data, uuid4): 'fulfillments': [user_ffill.to_dict()], 'conditions': [user_cond.to_dict()], 'operation': Transaction.CREATE, - 'timestamp': timestamp, 'metadata': None, 'asset': { 'id': uuid4, @@ -795,10 +790,9 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data, uuid4): asset = Asset(data, uuid4) tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset) tx_dict = tx.to_dict() - tx_dict.pop('id') tx_dict['transaction']['metadata'].pop('id') tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None - expected['transaction']['timestamp'] = tx_dict['transaction']['timestamp'] + tx_dict.pop('id') assert tx_dict == expected @@ -842,7 +836,6 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, metadata={'message': 'hello'}).to_dict() tx.pop('id') tx['transaction']['metadata'].pop('id') - tx['transaction'].pop('timestamp') tx['transaction'].pop('asset') assert tx == expected @@ -902,7 +895,6 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, tx_dict = tx.to_dict() tx_dict.pop('id') tx_dict['transaction']['metadata'].pop('id') - tx_dict['transaction'].pop('timestamp') tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None assert tx_dict == expected @@ -989,7 +981,6 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, expected_input = deepcopy(inputs[0]) expected['id'] = transfer_tx['id'] - expected['transaction']['timestamp'] = transfer_tx_body['timestamp'] expected_input.fulfillment.sign(serialize(expected).encode(), PrivateKey(user_priv)) expected_ffill = expected_input.fulfillment.serialize_uri() @@ -1058,7 +1049,6 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, transfer_tx = transfer_tx.to_dict() transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None - transfer_tx['transaction'].pop('timestamp') transfer_tx.pop('id') transfer_tx['transaction'].pop('asset') diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index dea4ea98..c3653506 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -682,7 +682,7 @@ class TestTransactionValidation(object): sleep(1) - signed_transfer_tx.timestamp = 123 + signed_transfer_tx.metadata.data = {'different': 1} # FIXME: https://github.com/bigchaindb/bigchaindb/issues/592 with pytest.raises(DoubleSpend): b.validate_transaction(signed_transfer_tx)