mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge remote-tracking branch 'origin/master' into remove-cid-fid
This commit is contained in:
commit
fc88c36ee5
7
.ci/travis-after-success.sh
Executable file
7
.ci/travis-after-success.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e -x
|
||||||
|
|
||||||
|
if [ "${TOXENV}" == "py35" ]; then
|
||||||
|
codecov
|
||||||
|
fi
|
8
.ci/travis-before-install.sh
Executable file
8
.ci/travis-before-install.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "${TOXENV}" == "py34" ] || [ "${TOXENV}" == "py35" ]; then
|
||||||
|
source /etc/lsb-release
|
||||||
|
echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | tee -a /etc/apt/sources.list.d/rethinkdb.list
|
||||||
|
wget -qO- https://download.rethinkdb.com/apt/pubkey.gpg | apt-key add -
|
||||||
|
apt-get update -qq
|
||||||
|
fi
|
7
.ci/travis-before-script.sh
Executable file
7
.ci/travis-before-script.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e -x
|
||||||
|
|
||||||
|
if [ "${TOXENV}" == "py34" ] || [ "${TOXENV}" == "py35" ]; then
|
||||||
|
rethinkdb --daemon
|
||||||
|
fi
|
11
.ci/travis-install.sh
Executable file
11
.ci/travis-install.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e -x
|
||||||
|
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install --upgrade tox
|
||||||
|
|
||||||
|
if [ "${TOXENV}" == "py34" ] || [ "${TOXENV}" == "py35" ]; then
|
||||||
|
sudo apt-get install rethinkdb
|
||||||
|
pip install --upgrade codecov
|
||||||
|
fi
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -77,3 +77,7 @@ benchmarking-tests/ssh_key.py
|
|||||||
# Ansible-specific files
|
# Ansible-specific files
|
||||||
ntools/one-m/ansible/hosts
|
ntools/one-m/ansible/hosts
|
||||||
ntools/one-m/ansible/ansible.cfg
|
ntools/one-m/ansible/ansible.cfg
|
||||||
|
|
||||||
|
# Just in time documentation
|
||||||
|
docs/server/source/schema
|
||||||
|
docs/server/source/drivers-clients/samples
|
||||||
|
30
.travis.yml
30
.travis.yml
@ -1,25 +1,19 @@
|
|||||||
sudo: required
|
|
||||||
language: python
|
language: python
|
||||||
cache: pip
|
cache: pip
|
||||||
python:
|
|
||||||
- 3.4
|
|
||||||
- 3.5
|
|
||||||
|
|
||||||
before_install:
|
env:
|
||||||
- source /etc/lsb-release
|
- TOXENV=py34
|
||||||
- echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee -a /etc/apt/sources.list.d/rethinkdb.list
|
- TOXENV=py35
|
||||||
- wget -qO- https://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -
|
- TOXENV=flake8
|
||||||
- sudo apt-get update -qq
|
- TOXENV=docsroot
|
||||||
|
- TOXENV=docsserver
|
||||||
|
|
||||||
install:
|
before_install: sudo .ci/travis-before-install.sh
|
||||||
- sudo apt-get install rethinkdb
|
|
||||||
- pip install -e .[test]
|
|
||||||
- pip install codecov
|
|
||||||
|
|
||||||
before_script:
|
install: .ci/travis-install.sh
|
||||||
- flake8 --max-line-length 119 bigchaindb/
|
|
||||||
- rethinkdb --daemon
|
|
||||||
|
|
||||||
script: py.test -n auto -s -v --cov=bigchaindb
|
before_script: .ci/travis-before-script.sh
|
||||||
|
|
||||||
after_success: codecov
|
script: tox -e ${TOXENV}
|
||||||
|
|
||||||
|
after_success: .ci/travis-after-success.sh
|
||||||
|
69
CHANGELOG.md
69
CHANGELOG.md
@ -1,4 +1,5 @@
|
|||||||
# Change Log (Release Notes)
|
# Change Log (Release Notes)
|
||||||
|
|
||||||
All _notable_ changes to this project will be documented in this file (`CHANGELOG.md`).
|
All _notable_ changes to this project will be documented in this file (`CHANGELOG.md`).
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/) (or at least we try).
|
This project adheres to [Semantic Versioning](http://semver.org/) (or at least we try).
|
||||||
Contributors to this file, please follow the guidelines on [keepachangelog.com](http://keepachangelog.com/).
|
Contributors to this file, please follow the guidelines on [keepachangelog.com](http://keepachangelog.com/).
|
||||||
@ -15,10 +16,74 @@ For reference, the possible headings are:
|
|||||||
* **Notes**
|
* **Notes**
|
||||||
|
|
||||||
|
|
||||||
|
## [0.8.0] - 2016-11-29
|
||||||
|
Tag name: v0.8.0
|
||||||
|
= commit:
|
||||||
|
committed:
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- The big new thing in version 0.8.0 is support for divisible assets, i.e. assets like carrots or thumbtacks, where the initial CREATE transaction can register/create some amount (e.g. 542 carrots), the first TRANSFER transaction can split that amount across multiple owners, and so on. [Pull Request #794](https://github.com/bigchaindb/bigchaindb/pull/794)
|
||||||
|
- Wrote a formal schema for the JSON structure of transactions. [Pull Request #798](https://github.com/bigchaindb/bigchaindb/pull/798)
|
||||||
|
- New configuration parameter: `backlog_reassign_delay`. [Pull Request #883](https://github.com/bigchaindb/bigchaindb/pull/883)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- CREATE transactions must now be signed by all `owners_before` (rather than by a federation node). [Pull Request #794](https://github.com/bigchaindb/bigchaindb/pull/794)
|
||||||
|
- The user-provided timestamp was removed from the transaction data model (schema). [Pull Request #817](https://github.com/bigchaindb/bigchaindb/pull/817)
|
||||||
|
- `get_transaction()` will now return a transaction from the backlog, even if there are copies of the transaction in invalid blocks. [Pull Request #793](https://github.com/bigchaindb/bigchaindb/pull/793)
|
||||||
|
- Several pull requests to introduce a generalized database interface, to move RethinkDB calls into a separate implementation of that interface, and to work on a new MongoDB implementation of that interface. Pull Requests
|
||||||
|
[#754](https://github.com/bigchaindb/bigchaindb/pull/754),
|
||||||
|
[#783](https://github.com/bigchaindb/bigchaindb/pull/783),
|
||||||
|
[#799](https://github.com/bigchaindb/bigchaindb/pull/799),
|
||||||
|
[#806](https://github.com/bigchaindb/bigchaindb/pull/806),
|
||||||
|
[#809](https://github.com/bigchaindb/bigchaindb/pull/809),
|
||||||
|
[#853](https://github.com/bigchaindb/bigchaindb/pull/853)
|
||||||
|
- Renamed "verifying key" to "public key". Renamed "signing key" to "private key". Renamed "vk" to "pk". [Pull Request #807](https://github.com/bigchaindb/bigchaindb/pull/807)
|
||||||
|
- `get_transaction_by_asset_id` now ignores invalid transactions. [Pull Request #810](https://github.com/bigchaindb/bigchaindb/pull/810)
|
||||||
|
- `get_transaction_by_metadata_id` now ignores invalid transactions. [Pull Request #811](https://github.com/bigchaindb/bigchaindb/pull/811)
|
||||||
|
- Updates to the configs and scripts for deploying a test network on AWS. The example config file deploys virtual machines running Ubuntu 16.04 now. Pull Requests
|
||||||
|
[#771](https://github.com/bigchaindb/bigchaindb/pull/771),
|
||||||
|
[#813](https://github.com/bigchaindb/bigchaindb/pull/813)
|
||||||
|
- Changed logging of transactions on block creation so now it just says the length of the list of transactions, rather than listing all the transactions. [Pull Request #861](https://github.com/bigchaindb/bigchaindb/pull/861)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Equality checks with AssetLinks. [Pull Request #825](https://github.com/bigchaindb/bigchaindb/pull/825)
|
||||||
|
- Bug in `bigchaindb load`. [Pull Request #824](https://github.com/bigchaindb/bigchaindb/pull/824)
|
||||||
|
- Two issues found with timestamp indexes. [Pull Request #816](https://github.com/bigchaindb/bigchaindb/pull/816)
|
||||||
|
- Hard-coded `backlog_reassign_delay`. [Pull Request #854](https://github.com/bigchaindb/bigchaindb/pull/854)
|
||||||
|
- Race condition in `test_stale_monitor.py`. [Pull Request #846](https://github.com/bigchaindb/bigchaindb/pull/846)
|
||||||
|
- When creating a signed vote, decode the vote signature to a `str`. [Pull Request #869](https://github.com/bigchaindb/bigchaindb/pull/869)
|
||||||
|
- Bug in AWS deployment scripts. Setting `BIND_HTTP_TO_LOCALHOST` to `False` didn't actually work. It does now. [Pull Request #870](https://github.com/bigchaindb/bigchaindb/pull/870)
|
||||||
|
|
||||||
|
### External Contributors
|
||||||
|
- @najlachamseddine - [Pull Request #528](https://github.com/bigchaindb/bigchaindb/pull/528)
|
||||||
|
- @ChristianGaertner - [Pull Request #659](https://github.com/bigchaindb/bigchaindb/pull/659)
|
||||||
|
- @MinchinWeb - [Pull Request #695](https://github.com/bigchaindb/bigchaindb/pull/695)
|
||||||
|
- @ckeyer - [Pull Request #785](https://github.com/bigchaindb/bigchaindb/pull/785)
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- @ChristianGaertner added a Python style checker (Flake8) to Travis CI, so external contributors should be aware that the Python code in their pull requests will be checked. See [our Python Style Guide](PYTHON_STYLE_GUIDE.md).
|
||||||
|
- Several additions and changes to the documentation, e.g. Pull Requests
|
||||||
|
[#690](https://github.com/bigchaindb/bigchaindb/pull/690),
|
||||||
|
[#764](https://github.com/bigchaindb/bigchaindb/pull/764),
|
||||||
|
[#766](https://github.com/bigchaindb/bigchaindb/pull/766),
|
||||||
|
[#769](https://github.com/bigchaindb/bigchaindb/pull/769),
|
||||||
|
[#777](https://github.com/bigchaindb/bigchaindb/pull/777),
|
||||||
|
[#800](https://github.com/bigchaindb/bigchaindb/pull/800),
|
||||||
|
[#801](https://github.com/bigchaindb/bigchaindb/pull/801),
|
||||||
|
[#802](https://github.com/bigchaindb/bigchaindb/pull/802),
|
||||||
|
[#803](https://github.com/bigchaindb/bigchaindb/pull/803),
|
||||||
|
[#819](https://github.com/bigchaindb/bigchaindb/pull/819),
|
||||||
|
[#827](https://github.com/bigchaindb/bigchaindb/pull/827),
|
||||||
|
[#859](https://github.com/bigchaindb/bigchaindb/pull/859),
|
||||||
|
[#872](https://github.com/bigchaindb/bigchaindb/pull/872),
|
||||||
|
[#882](https://github.com/bigchaindb/bigchaindb/pull/882),
|
||||||
|
[#883](https://github.com/bigchaindb/bigchaindb/pull/883)
|
||||||
|
|
||||||
|
|
||||||
## [0.7.0] - 2016-10-28
|
## [0.7.0] - 2016-10-28
|
||||||
Tag name: v0.7.0
|
Tag name: v0.7.0
|
||||||
= commit:
|
= commit: 2dd7f1af27478c529e6d2d916f64daa3fbda3885
|
||||||
committed:
|
committed: Oct 28, 2016, 4:00 PM GMT+2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Stale transactions in the `backlog` table now get reassigned to another node (for inclusion in a new block): [Pull Request #359](https://github.com/bigchaindb/bigchaindb/pull/359)
|
- Stale transactions in the `backlog` table now get reassigned to another node (for inclusion in a new block): [Pull Request #359](https://github.com/bigchaindb/bigchaindb/pull/359)
|
||||||
|
@ -17,7 +17,9 @@ WORKDIR /data
|
|||||||
|
|
||||||
ENV BIGCHAINDB_CONFIG_PATH /data/.bigchaindb
|
ENV BIGCHAINDB_CONFIG_PATH /data/.bigchaindb
|
||||||
ENV BIGCHAINDB_SERVER_BIND 0.0.0.0:9984
|
ENV BIGCHAINDB_SERVER_BIND 0.0.0.0:9984
|
||||||
ENV BIGCHAINDB_API_ENDPOINT http://bigchaindb:9984/api/v1
|
# BigchainDB Server doesn't need BIGCHAINDB_API_ENDPOINT any more
|
||||||
|
# but maybe our Docker or Docker Compose stuff does?
|
||||||
|
# ENV BIGCHAINDB_API_ENDPOINT http://bigchaindb:9984/api/v1
|
||||||
|
|
||||||
ENTRYPOINT ["bigchaindb", "--dev-start-rethinkdb", "--dev-allow-temp-keypair"]
|
ENTRYPOINT ["bigchaindb", "--dev-start-rethinkdb", "--dev-allow-temp-keypair"]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM python:3.5
|
FROM python:3.5
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update && apt-get install -y python3.4 vim
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
RUN mkdir -p /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
@ -73,13 +73,13 @@ flake8 --max-line-length 119 bigchaindb/
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Writing and Running (Python) Unit Tests
|
## Writing and Running (Python) Tests
|
||||||
|
|
||||||
We write unit tests for our Python code using the [pytest](http://pytest.org/latest/) framework.
|
We write unit and integration tests for our Python code using the [pytest](http://pytest.org/latest/) framework.
|
||||||
|
|
||||||
All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples.
|
All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples.
|
||||||
|
|
||||||
You can run all unit tests using:
|
You can run all tests using:
|
||||||
```text
|
```text
|
||||||
py.test -v
|
py.test -v
|
||||||
```
|
```
|
||||||
@ -96,4 +96,27 @@ python setup.py test
|
|||||||
|
|
||||||
If you want to learn about all the things you can do with pytest, see [the pytest documentation](http://pytest.org/latest/).
|
If you want to learn about all the things you can do with pytest, see [the pytest documentation](http://pytest.org/latest/).
|
||||||
|
|
||||||
**Automated testing of pull requests.** We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.)
|
### Tox
|
||||||
|
|
||||||
|
We use [tox](https://tox.readthedocs.io/en/latest/) to run multiple suites of tests against multiple environments during automated testing. Generally you don't need to run this yourself, but it might be useful when troubleshooting a failing CI build.
|
||||||
|
|
||||||
|
To run all the tox tests, use:
|
||||||
|
```text
|
||||||
|
tox
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
```text
|
||||||
|
python -m tox
|
||||||
|
```
|
||||||
|
|
||||||
|
To run only a few environments, use the `-e` flag:
|
||||||
|
```text
|
||||||
|
tox -e {ENVLIST}
|
||||||
|
```
|
||||||
|
|
||||||
|
where `{ENVLIST}` is one or more of the environments specified in the [tox.ini file](tox.ini).
|
||||||
|
|
||||||
|
### Automated testing of pull requests
|
||||||
|
|
||||||
|
We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
This is a summary of the steps we go through to release a new version of BigchainDB Server.
|
This is a summary of the steps we go through to release a new version of BigchainDB Server.
|
||||||
|
|
||||||
1. Run `python docs/server/generate_schema_documentation.py` and commit the changes in `docs/server/source/schema/`, if any.
|
|
||||||
1. Update the `CHANGELOG.md` file
|
1. Update the `CHANGELOG.md` file
|
||||||
1. Update the version numbers in `bigchaindb/version.py`. Note that we try to use [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH)
|
1. Update the version numbers in `bigchaindb/version.py`. Note that we try to use [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH)
|
||||||
1. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
|
1. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
|
||||||
|
@ -29,7 +29,6 @@ config = {
|
|||||||
'port': 8125,
|
'port': 8125,
|
||||||
'rate': 0.01,
|
'rate': 0.01,
|
||||||
},
|
},
|
||||||
'api_endpoint': os.environ.get('BIGCHAINDB_API_ENDPOINT') or 'http://localhost:9984/api/v1',
|
|
||||||
'backlog_reassign_delay': 120
|
'backlog_reassign_delay': 120
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ def validate_transaction_schema(tx_body):
|
|||||||
try:
|
try:
|
||||||
jsonschema.validate(tx_body, TX_SCHEMA)
|
jsonschema.validate(tx_body, TX_SCHEMA)
|
||||||
except jsonschema.ValidationError as exc:
|
except jsonschema.ValidationError as exc:
|
||||||
raise SchemaValidationError(str(exc))
|
raise SchemaValidationError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['TX_SCHEMA', 'TX_SCHEMA_YAML', 'validate_transaction_schema']
|
__all__ = ['TX_SCHEMA', 'TX_SCHEMA_YAML', 'validate_transaction_schema']
|
||||||
|
@ -5,10 +5,14 @@ type: object
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
title: Transaction Schema
|
title: Transaction Schema
|
||||||
description: |
|
description: |
|
||||||
This is the outer transaction wrapper. It contains the ID, version and the body of the transaction, which is also called ``transaction``.
|
A transaction represents the creation or transfer of assets in BigchainDB.
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- transaction
|
- fulfillments
|
||||||
|
- conditions
|
||||||
|
- operation
|
||||||
|
- metadata
|
||||||
|
- asset
|
||||||
- version
|
- version
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
@ -18,51 +22,38 @@ properties:
|
|||||||
derived hashes and signatures from the transaction, serializing it to
|
derived hashes and signatures from the transaction, serializing it to
|
||||||
JSON with keys in sorted order and then hashing the resulting string
|
JSON with keys in sorted order and then hashing the resulting string
|
||||||
with sha3.
|
with sha3.
|
||||||
transaction:
|
operation:
|
||||||
type: object
|
"$ref": "#/definitions/operation"
|
||||||
title: transaction
|
asset:
|
||||||
|
"$ref": "#/definitions/asset"
|
||||||
description: |
|
description: |
|
||||||
See: `Transaction Body`_.
|
Description of the asset being transacted.
|
||||||
additionalProperties: false
|
|
||||||
required:
|
|
||||||
- fulfillments
|
|
||||||
- conditions
|
|
||||||
- operation
|
|
||||||
- metadata
|
|
||||||
- asset
|
|
||||||
properties:
|
|
||||||
operation:
|
|
||||||
"$ref": "#/definitions/operation"
|
|
||||||
asset:
|
|
||||||
"$ref": "#/definitions/asset"
|
|
||||||
description: |
|
|
||||||
Description of the asset being transacted.
|
|
||||||
|
|
||||||
See: `Asset`_.
|
See: `Asset`_.
|
||||||
fulfillments:
|
fulfillments:
|
||||||
type: array
|
type: array
|
||||||
title: "Fulfillments list"
|
title: "Fulfillments list"
|
||||||
description: |
|
description: |
|
||||||
Array of the fulfillments (inputs) of a transaction.
|
Array of the fulfillments (inputs) of a transaction.
|
||||||
|
|
||||||
See: Fulfillment_.
|
See: Fulfillment_.
|
||||||
items:
|
items:
|
||||||
"$ref": "#/definitions/fulfillment"
|
"$ref": "#/definitions/fulfillment"
|
||||||
conditions:
|
conditions:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
Array of conditions (outputs) provided by this transaction.
|
Array of conditions (outputs) provided by this transaction.
|
||||||
|
|
||||||
See: Condition_.
|
See: Condition_.
|
||||||
items:
|
items:
|
||||||
"$ref": "#/definitions/condition"
|
"$ref": "#/definitions/condition"
|
||||||
metadata:
|
metadata:
|
||||||
"$ref": "#/definitions/metadata"
|
"$ref": "#/definitions/metadata"
|
||||||
description: |
|
description: |
|
||||||
User provided transaction metadata. This field may be ``null`` or may
|
User provided transaction metadata. This field may be ``null`` or may
|
||||||
contain an id and an object with freeform metadata.
|
contain an id and an object with freeform metadata.
|
||||||
|
|
||||||
See: `Metadata`_.
|
See: `Metadata`_.
|
||||||
version:
|
version:
|
||||||
type: integer
|
type: integer
|
||||||
minimum: 1
|
minimum: 1
|
||||||
@ -236,17 +227,7 @@ definitions:
|
|||||||
- type: object
|
- type: object
|
||||||
description: |
|
description: |
|
||||||
User provided transaction metadata. This field may be ``null`` or may
|
User provided transaction metadata. This field may be ``null`` or may
|
||||||
contain an id and an object with freeform metadata.
|
contain an non empty object with freeform metadata.
|
||||||
additionalProperties: false
|
additionalProperties: true
|
||||||
required:
|
minProperties: 1
|
||||||
- id
|
|
||||||
- data
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
"$ref": "#/definitions/uuid4"
|
|
||||||
data:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
User provided transaction metadata.
|
|
||||||
additionalProperties: true
|
|
||||||
- type: 'null'
|
- type: 'null'
|
||||||
|
@ -556,66 +556,6 @@ class AssetLink(Asset):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Metadata(object):
|
|
||||||
"""Metadata is used to store a dictionary and its hash in a Transaction."""
|
|
||||||
|
|
||||||
def __init__(self, data=None, data_id=None):
|
|
||||||
"""Metadata stores a payload `data` as well as data's hash, `data_id`.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
When no `data_id` is provided, one is being generated by
|
|
||||||
this method.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (dict): A dictionary to be held by Metadata.
|
|
||||||
data_id (str): A hash corresponding to the contents of
|
|
||||||
`data`.
|
|
||||||
"""
|
|
||||||
if data is not None and not isinstance(data, dict):
|
|
||||||
raise TypeError('`data` must be a dict instance or None')
|
|
||||||
|
|
||||||
self.data_id = data_id if data_id is not None else self.to_hash()
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
# TODO: If `other !== Data` return `False`
|
|
||||||
return self.to_dict() == other.to_dict()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data):
|
|
||||||
"""Transforms a Python dictionary to a Metadata object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (dict): The dictionary to be serialized.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
:class:`~bigchaindb.common.transaction.Metadata`
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return cls(data['data'], data['id'])
|
|
||||||
except TypeError:
|
|
||||||
return cls()
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
"""Transforms the object to a Python dictionary.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(dict|None): The Metadata object as an alternative
|
|
||||||
serialization format.
|
|
||||||
"""
|
|
||||||
if self.data is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'data': self.data,
|
|
||||||
'id': self.data_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_hash(self):
|
|
||||||
"""A hash corresponding to the contents of `payload`."""
|
|
||||||
return str(uuid4())
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(object):
|
class Transaction(object):
|
||||||
"""A Transaction is used to create and transfer assets.
|
"""A Transaction is used to create and transfer assets.
|
||||||
|
|
||||||
@ -630,7 +570,7 @@ class Transaction(object):
|
|||||||
spend.
|
spend.
|
||||||
conditions (:obj:`list` of :class:`~bigchaindb.common.
|
conditions (:obj:`list` of :class:`~bigchaindb.common.
|
||||||
transaction.Condition`, optional): Define the assets to lock.
|
transaction.Condition`, optional): Define the assets to lock.
|
||||||
metadata (:class:`~bigchaindb.common.transaction.Metadata`):
|
metadata (dict):
|
||||||
Metadata to be stored along with the Transaction.
|
Metadata to be stored along with the Transaction.
|
||||||
version (int): Defines the version number of a Transaction.
|
version (int): Defines the version number of a Transaction.
|
||||||
"""
|
"""
|
||||||
@ -658,7 +598,7 @@ class Transaction(object):
|
|||||||
conditions (:obj:`list` of :class:`~bigchaindb.common.
|
conditions (:obj:`list` of :class:`~bigchaindb.common.
|
||||||
transaction.Condition`, optional): Define the assets to
|
transaction.Condition`, optional): Define the assets to
|
||||||
lock.
|
lock.
|
||||||
metadata (:class:`~bigchaindb.common.transaction.Metadata`):
|
metadata (dict):
|
||||||
Metadata to be stored along with the Transaction.
|
Metadata to be stored along with the Transaction.
|
||||||
version (int): Defines the version number of a Transaction.
|
version (int): Defines the version number of a Transaction.
|
||||||
|
|
||||||
@ -679,8 +619,8 @@ class Transaction(object):
|
|||||||
if fulfillments and not isinstance(fulfillments, list):
|
if fulfillments and not isinstance(fulfillments, list):
|
||||||
raise TypeError('`fulfillments` must be a list instance or None')
|
raise TypeError('`fulfillments` must be a list instance or None')
|
||||||
|
|
||||||
if metadata is not None and not isinstance(metadata, Metadata):
|
if metadata is not None and not isinstance(metadata, dict):
|
||||||
raise TypeError('`metadata` must be a Metadata instance or None')
|
raise TypeError('`metadata` must be a dict or None')
|
||||||
|
|
||||||
self.version = version if version is not None else self.VERSION
|
self.version = version if version is not None else self.VERSION
|
||||||
self.operation = operation
|
self.operation = operation
|
||||||
@ -734,7 +674,6 @@ class Transaction(object):
|
|||||||
if len(owners_after) == 0:
|
if len(owners_after) == 0:
|
||||||
raise ValueError('`owners_after` list cannot be empty')
|
raise ValueError('`owners_after` list cannot be empty')
|
||||||
|
|
||||||
metadata = Metadata(metadata)
|
|
||||||
fulfillments = []
|
fulfillments = []
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
@ -809,7 +748,6 @@ class Transaction(object):
|
|||||||
pub_keys, amount = owner_after
|
pub_keys, amount = owner_after
|
||||||
conditions.append(Condition.generate(pub_keys, amount))
|
conditions.append(Condition.generate(pub_keys, amount))
|
||||||
|
|
||||||
metadata = Metadata(metadata)
|
|
||||||
inputs = deepcopy(inputs)
|
inputs = deepcopy(inputs)
|
||||||
return cls(cls.TRANSFER, asset, inputs, conditions, metadata)
|
return cls(cls.TRANSFER, asset, inputs, conditions, metadata)
|
||||||
|
|
||||||
@ -1151,30 +1089,21 @@ class Transaction(object):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: The Transaction as an alternative serialization format.
|
dict: The Transaction as an alternative serialization format.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
metadata = self.metadata.to_dict()
|
|
||||||
except AttributeError:
|
|
||||||
# NOTE: metadata can be None and that's OK
|
|
||||||
metadata = None
|
|
||||||
|
|
||||||
if self.operation in (self.__class__.GENESIS, self.__class__.CREATE):
|
if self.operation in (self.__class__.GENESIS, self.__class__.CREATE):
|
||||||
asset = self.asset.to_dict()
|
asset = self.asset.to_dict()
|
||||||
else:
|
else:
|
||||||
# NOTE: An `asset` in a `TRANSFER` only contains the asset's id
|
# NOTE: An `asset` in a `TRANSFER` only contains the asset's id
|
||||||
asset = {'id': self.asset.data_id}
|
asset = {'id': self.asset.data_id}
|
||||||
|
|
||||||
tx_body = {
|
tx = {
|
||||||
'fulfillments': [fulfillment.to_dict() for fulfillment
|
'fulfillments': [fulfillment.to_dict() for fulfillment
|
||||||
in self.fulfillments],
|
in self.fulfillments],
|
||||||
'conditions': [condition.to_dict() for condition
|
'conditions': [condition.to_dict() for condition
|
||||||
in self.conditions],
|
in self.conditions],
|
||||||
'operation': str(self.operation),
|
'operation': str(self.operation),
|
||||||
'metadata': metadata,
|
'metadata': self.metadata,
|
||||||
'asset': asset,
|
'asset': asset,
|
||||||
}
|
|
||||||
tx = {
|
|
||||||
'version': self.version,
|
'version': self.version,
|
||||||
'transaction': tx_body,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tx_no_signatures = Transaction._remove_signatures(tx)
|
tx_no_signatures = Transaction._remove_signatures(tx)
|
||||||
@ -1199,7 +1128,7 @@ class Transaction(object):
|
|||||||
# NOTE: We remove the reference since we need `tx_dict` only for the
|
# NOTE: We remove the reference since we need `tx_dict` only for the
|
||||||
# transaction's hash
|
# transaction's hash
|
||||||
tx_dict = deepcopy(tx_dict)
|
tx_dict = deepcopy(tx_dict)
|
||||||
for fulfillment in tx_dict['transaction']['fulfillments']:
|
for fulfillment in tx_dict['fulfillments']:
|
||||||
# NOTE: Not all Cryptoconditions return a `signature` key (e.g.
|
# NOTE: Not all Cryptoconditions return a `signature` key (e.g.
|
||||||
# ThresholdSha256Fulfillment), so setting it to `None` in any
|
# ThresholdSha256Fulfillment), so setting it to `None` in any
|
||||||
# case could yield incorrect signatures. This is why we only
|
# case could yield incorrect signatures. This is why we only
|
||||||
@ -1239,17 +1168,19 @@ class Transaction(object):
|
|||||||
try:
|
try:
|
||||||
proposed_tx_id = tx_body.pop('id')
|
proposed_tx_id = tx_body.pop('id')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidHash()
|
raise InvalidHash('No transaction id found!')
|
||||||
|
|
||||||
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
||||||
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
||||||
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
||||||
|
|
||||||
if proposed_tx_id != valid_tx_id:
|
if proposed_tx_id != valid_tx_id:
|
||||||
raise InvalidHash()
|
err_msg = ("The transaction's id '{}' isn't equal to "
|
||||||
|
"the hash of its body, i.e. it's not valid.")
|
||||||
|
raise InvalidHash(err_msg.format(proposed_tx_id))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, tx_body):
|
def from_dict(cls, tx):
|
||||||
"""Transforms a Python dictionary to a Transaction object.
|
"""Transforms a Python dictionary to a Transaction object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1258,17 +1189,15 @@ class Transaction(object):
|
|||||||
Returns:
|
Returns:
|
||||||
:class:`~bigchaindb.common.transaction.Transaction`
|
:class:`~bigchaindb.common.transaction.Transaction`
|
||||||
"""
|
"""
|
||||||
cls.validate_structure(tx_body)
|
cls.validate_structure(tx)
|
||||||
tx = tx_body['transaction']
|
|
||||||
fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment
|
fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment
|
||||||
in tx['fulfillments']]
|
in tx['fulfillments']]
|
||||||
conditions = [Condition.from_dict(condition) for condition
|
conditions = [Condition.from_dict(condition) for condition
|
||||||
in tx['conditions']]
|
in tx['conditions']]
|
||||||
metadata = Metadata.from_dict(tx['metadata'])
|
|
||||||
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
|
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
|
||||||
asset = Asset.from_dict(tx['asset'])
|
asset = Asset.from_dict(tx['asset'])
|
||||||
else:
|
else:
|
||||||
asset = AssetLink.from_dict(tx['asset'])
|
asset = AssetLink.from_dict(tx['asset'])
|
||||||
|
|
||||||
return cls(tx['operation'], asset, fulfillments, conditions,
|
return cls(tx['operation'], asset, fulfillments, conditions,
|
||||||
metadata, tx_body['version'])
|
tx['metadata'], tx['version'])
|
||||||
|
@ -330,35 +330,6 @@ class Bigchain(object):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_transaction_by_metadata_id(self, metadata_id):
|
|
||||||
"""Retrieves valid or undecided transactions related to a particular
|
|
||||||
metadata.
|
|
||||||
|
|
||||||
When creating a transaction one of the optional arguments is the
|
|
||||||
`metadata`. The metadata is a generic dict that contains extra
|
|
||||||
information that can be appended to the transaction.
|
|
||||||
|
|
||||||
To make it easy to query the bigchain for that particular metadata we
|
|
||||||
create a UUID for the metadata and store it with the transaction.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
metadata_id (str): the id for this particular metadata.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of valid or undecided transactions containing that metadata.
|
|
||||||
If no transaction exists with that metadata it returns an empty
|
|
||||||
list `[]`
|
|
||||||
"""
|
|
||||||
txids = self.backend.get_txids_by_metadata_id(metadata_id)
|
|
||||||
transactions = []
|
|
||||||
for txid in txids:
|
|
||||||
tx = self.get_transaction(txid)
|
|
||||||
# if a valid or undecided transaction exists append it to the list
|
|
||||||
# of transactions
|
|
||||||
if tx:
|
|
||||||
transactions.append(tx)
|
|
||||||
return transactions
|
|
||||||
|
|
||||||
def get_transactions_by_asset_id(self, asset_id):
|
def get_transactions_by_asset_id(self, asset_id):
|
||||||
"""Retrieves valid or undecided transactions related to a particular
|
"""Retrieves valid or undecided transactions related to a particular
|
||||||
asset.
|
asset.
|
||||||
@ -396,7 +367,7 @@ class Bigchain(object):
|
|||||||
cursor = self.backend.get_asset_by_id(asset_id)
|
cursor = self.backend.get_asset_by_id(asset_id)
|
||||||
cursor = list(cursor)
|
cursor = list(cursor)
|
||||||
if cursor:
|
if cursor:
|
||||||
return Asset.from_dict(cursor[0]['transaction']['asset'])
|
return Asset.from_dict(cursor[0]['asset'])
|
||||||
|
|
||||||
def get_spent(self, txid, cid):
|
def get_spent(self, txid, cid):
|
||||||
"""Check if a `txid` was already used as an input.
|
"""Check if a `txid` was already used as an input.
|
||||||
@ -440,13 +411,13 @@ class Bigchain(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_owned_ids(self, owner):
|
def get_owned_ids(self, owner):
|
||||||
"""Retrieve a list of `txid`s that can be used as inputs.
|
"""Retrieve a list of ``txid`` s that can be used as inputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
owner (str): base58 encoded public key.
|
owner (str): base58 encoded public key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`list` of TransactionLink: list of `txid`s and `cid`s
|
:obj:`list` of TransactionLink: list of ``txid`` s and ``cid`` s
|
||||||
pointing to another transaction's condition
|
pointing to another transaction's condition
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -465,7 +436,7 @@ class Bigchain(object):
|
|||||||
# use it after the execution of this function.
|
# use it after the execution of this function.
|
||||||
# a transaction can contain multiple outputs (conditions) so we need to iterate over all of them
|
# a transaction can contain multiple outputs (conditions) so we need to iterate over all of them
|
||||||
# to get a list of outputs available to spend
|
# to get a list of outputs available to spend
|
||||||
for index, cond in enumerate(tx['transaction']['conditions']):
|
for index, cond in enumerate(tx['conditions']):
|
||||||
# for simple signature conditions there are no subfulfillments
|
# for simple signature conditions there are no subfulfillments
|
||||||
# check if the owner is in the condition `owners_after`
|
# check if the owner is in the condition `owners_after`
|
||||||
if len(cond['owners_after']) == 1:
|
if len(cond['owners_after']) == 1:
|
||||||
|
@ -138,32 +138,6 @@ class RethinkDBBackend:
|
|||||||
.get_all(transaction_id, index='transaction_id')
|
.get_all(transaction_id, index='transaction_id')
|
||||||
.pluck('votes', 'id', {'block': ['voters']}))
|
.pluck('votes', 'id', {'block': ['voters']}))
|
||||||
|
|
||||||
def get_txids_by_metadata_id(self, metadata_id):
|
|
||||||
"""Retrieves transaction ids related to a particular metadata.
|
|
||||||
|
|
||||||
When creating a transaction one of the optional arguments is the
|
|
||||||
`metadata`. The metadata is a generic dict that contains extra
|
|
||||||
information that can be appended to the transaction.
|
|
||||||
|
|
||||||
To make it easy to query the bigchain for that particular metadata we
|
|
||||||
create a UUID for the metadata and store it with the transaction.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
metadata_id (str): the id for this particular metadata.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of transaction ids containing that metadata. If no
|
|
||||||
transaction exists with that metadata it returns an empty list `[]`
|
|
||||||
"""
|
|
||||||
return self.connection.run(
|
|
||||||
r.table('bigchain', read_mode=self.read_mode)
|
|
||||||
.get_all(metadata_id, index='metadata_id')
|
|
||||||
.concat_map(lambda block: block['block']['transactions'])
|
|
||||||
.filter(lambda transaction:
|
|
||||||
transaction['transaction']['metadata']['id'] ==
|
|
||||||
metadata_id)
|
|
||||||
.get_field('id'))
|
|
||||||
|
|
||||||
def get_txids_by_asset_id(self, asset_id):
|
def get_txids_by_asset_id(self, asset_id):
|
||||||
"""Retrieves transactions ids related to a particular asset.
|
"""Retrieves transactions ids related to a particular asset.
|
||||||
|
|
||||||
@ -185,7 +159,7 @@ class RethinkDBBackend:
|
|||||||
r.table('bigchain', read_mode=self.read_mode)
|
r.table('bigchain', read_mode=self.read_mode)
|
||||||
.get_all(asset_id, index='asset_id')
|
.get_all(asset_id, index='asset_id')
|
||||||
.concat_map(lambda block: block['block']['transactions'])
|
.concat_map(lambda block: block['block']['transactions'])
|
||||||
.filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id)
|
.filter(lambda transaction: transaction['asset']['id'] == asset_id)
|
||||||
.get_field('id'))
|
.get_field('id'))
|
||||||
|
|
||||||
def get_asset_by_id(self, asset_id):
|
def get_asset_by_id(self, asset_id):
|
||||||
@ -202,10 +176,10 @@ class RethinkDBBackend:
|
|||||||
.get_all(asset_id, index='asset_id')
|
.get_all(asset_id, index='asset_id')
|
||||||
.concat_map(lambda block: block['block']['transactions'])
|
.concat_map(lambda block: block['block']['transactions'])
|
||||||
.filter(lambda transaction:
|
.filter(lambda transaction:
|
||||||
transaction['transaction']['asset']['id'] == asset_id)
|
transaction['asset']['id'] == asset_id)
|
||||||
.filter(lambda transaction:
|
.filter(lambda transaction:
|
||||||
transaction['transaction']['operation'] == 'CREATE')
|
transaction['operation'] == 'CREATE')
|
||||||
.pluck({'transaction': 'asset'}))
|
.pluck('asset'))
|
||||||
|
|
||||||
def get_spent(self, transaction_id, condition_id):
|
def get_spent(self, transaction_id, condition_id):
|
||||||
"""Check if a `txid` was already used as an input.
|
"""Check if a `txid` was already used as an input.
|
||||||
@ -225,7 +199,7 @@ class RethinkDBBackend:
|
|||||||
return self.connection.run(
|
return self.connection.run(
|
||||||
r.table('bigchain', read_mode=self.read_mode)
|
r.table('bigchain', read_mode=self.read_mode)
|
||||||
.concat_map(lambda doc: doc['block']['transactions'])
|
.concat_map(lambda doc: doc['block']['transactions'])
|
||||||
.filter(lambda transaction: transaction['transaction']['fulfillments'].contains(
|
.filter(lambda transaction: transaction['fulfillments'].contains(
|
||||||
lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id})))
|
lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id})))
|
||||||
|
|
||||||
def get_owned_ids(self, owner):
|
def get_owned_ids(self, owner):
|
||||||
@ -242,7 +216,7 @@ class RethinkDBBackend:
|
|||||||
return self.connection.run(
|
return self.connection.run(
|
||||||
r.table('bigchain', read_mode=self.read_mode)
|
r.table('bigchain', read_mode=self.read_mode)
|
||||||
.concat_map(lambda doc: doc['block']['transactions'])
|
.concat_map(lambda doc: doc['block']['transactions'])
|
||||||
.filter(lambda tx: tx['transaction']['conditions'].contains(
|
.filter(lambda tx: tx['conditions'].contains(
|
||||||
lambda c: c['owners_after'].contains(owner))))
|
lambda c: c['owners_after'].contains(owner))))
|
||||||
|
|
||||||
def get_votes_by_block_id(self, block_id):
|
def get_votes_by_block_id(self, block_id):
|
||||||
|
@ -116,15 +116,10 @@ def create_bigchain_secondary_index(conn, dbname):
|
|||||||
.index_create('transaction_id',
|
.index_create('transaction_id',
|
||||||
r.row['block']['transactions']['id'], multi=True)\
|
r.row['block']['transactions']['id'], multi=True)\
|
||||||
.run(conn)
|
.run(conn)
|
||||||
# secondary index for payload data by UUID
|
|
||||||
r.db(dbname).table('bigchain')\
|
|
||||||
.index_create('metadata_id',
|
|
||||||
r.row['block']['transactions']['transaction']['metadata']['id'], multi=True)\
|
|
||||||
.run(conn)
|
|
||||||
# secondary index for asset uuid
|
# secondary index for asset uuid
|
||||||
r.db(dbname).table('bigchain')\
|
r.db(dbname).table('bigchain')\
|
||||||
.index_create('asset_id',
|
.index_create('asset_id',
|
||||||
r.row['block']['transactions']['transaction']['asset']['id'], multi=True)\
|
r.row['block']['transactions']['asset']['id'], multi=True)\
|
||||||
.run(conn)
|
.run(conn)
|
||||||
|
|
||||||
# wait for rethinkdb to finish creating secondary indexes
|
# wait for rethinkdb to finish creating secondary indexes
|
||||||
|
@ -6,6 +6,7 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
|
|||||||
AssetIdMismatch, AmountError)
|
AssetIdMismatch, AmountError)
|
||||||
from bigchaindb.common.transaction import Transaction, Asset
|
from bigchaindb.common.transaction import Transaction, Asset
|
||||||
from bigchaindb.common.util import gen_timestamp, serialize
|
from bigchaindb.common.util import gen_timestamp, serialize
|
||||||
|
from bigchaindb.common.schema import validate_transaction_schema
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Transaction):
|
class Transaction(Transaction):
|
||||||
@ -113,6 +114,11 @@ class Transaction(Transaction):
|
|||||||
else:
|
else:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, tx_body):
|
||||||
|
validate_transaction_schema(tx_body)
|
||||||
|
return super().from_dict(tx_body)
|
||||||
|
|
||||||
|
|
||||||
class Block(object):
|
class Block(object):
|
||||||
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
|
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
|
||||||
|
@ -8,7 +8,7 @@ function.
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import rethinkdb as r
|
import rethinkdb as r
|
||||||
from multipipes import Pipeline, Node
|
from multipipes import Pipeline, Node, Pipe
|
||||||
|
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
from bigchaindb.pipelines.utils import ChangeFeed
|
from bigchaindb.pipelines.utils import ChangeFeed
|
||||||
@ -161,6 +161,7 @@ def create_pipeline():
|
|||||||
block_pipeline = BlockPipeline()
|
block_pipeline = BlockPipeline()
|
||||||
|
|
||||||
pipeline = Pipeline([
|
pipeline = Pipeline([
|
||||||
|
Pipe(maxsize=1000),
|
||||||
Node(block_pipeline.filter_tx),
|
Node(block_pipeline.filter_tx),
|
||||||
Node(block_pipeline.validate_tx, fraction_of_cores=1),
|
Node(block_pipeline.validate_tx, fraction_of_cores=1),
|
||||||
Node(block_pipeline.create, timeout=1),
|
Node(block_pipeline.create, timeout=1),
|
||||||
|
@ -156,4 +156,4 @@ def is_genesis_block(block):
|
|||||||
try:
|
try:
|
||||||
return block.transactions[0].operation == 'GENESIS'
|
return block.transactions[0].operation == 'GENESIS'
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return block['block']['transactions'][0]['transaction']['operation'] == 'GENESIS'
|
return block['block']['transactions'][0]['operation'] == 'GENESIS'
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
__version__ = '0.8.0.dev'
|
__version__ = '0.9.0.dev'
|
||||||
__short_version__ = '0.8.dev'
|
__short_version__ = '0.9.dev'
|
||||||
|
@ -20,6 +20,5 @@ def home():
|
|||||||
'software': 'BigchainDB',
|
'software': 'BigchainDB',
|
||||||
'version': version.__version__,
|
'version': version.__version__,
|
||||||
'public_key': bigchaindb.config['keypair']['public'],
|
'public_key': bigchaindb.config['keypair']['public'],
|
||||||
'keyring': bigchaindb.config['keyring'],
|
'keyring': bigchaindb.config['keyring']
|
||||||
'api_endpoint': bigchaindb.config['api_endpoint']
|
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,18 @@ For more information please refer to the documentation on ReadTheDocs:
|
|||||||
from flask import current_app, request, Blueprint
|
from flask import current_app, request, Blueprint
|
||||||
from flask_restful import Resource, Api
|
from flask_restful import Resource, Api
|
||||||
|
|
||||||
from bigchaindb.common.exceptions import ValidationError, InvalidSignature
|
from bigchaindb.common.exceptions import (
|
||||||
|
AmountError,
|
||||||
|
DoubleSpend,
|
||||||
|
InvalidHash,
|
||||||
|
InvalidSignature,
|
||||||
|
SchemaValidationError,
|
||||||
|
OperationError,
|
||||||
|
TransactionDoesNotExist,
|
||||||
|
TransactionOwnerError,
|
||||||
|
TransactionNotInValidBlock,
|
||||||
|
ValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
@ -98,16 +109,38 @@ class TransactionListApi(Resource):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
tx_obj = Transaction.from_dict(tx)
|
tx_obj = Transaction.from_dict(tx)
|
||||||
except (ValidationError, InvalidSignature):
|
except SchemaValidationError as e:
|
||||||
return make_error(400, 'Invalid transaction')
|
return make_error(
|
||||||
|
400,
|
||||||
|
message='Invalid transaction schema: {}'.format(
|
||||||
|
e.__cause__.message)
|
||||||
|
)
|
||||||
|
except (ValidationError, InvalidSignature) as e:
|
||||||
|
return make_error(
|
||||||
|
400,
|
||||||
|
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
||||||
|
)
|
||||||
|
|
||||||
with pool() as bigchain:
|
with pool() as bigchain:
|
||||||
if bigchain.is_valid_transaction(tx_obj):
|
try:
|
||||||
|
bigchain.validate_transaction(tx_obj)
|
||||||
|
except (ValueError,
|
||||||
|
OperationError,
|
||||||
|
TransactionDoesNotExist,
|
||||||
|
TransactionOwnerError,
|
||||||
|
DoubleSpend,
|
||||||
|
InvalidHash,
|
||||||
|
InvalidSignature,
|
||||||
|
TransactionNotInValidBlock,
|
||||||
|
AmountError) as e:
|
||||||
|
return make_error(
|
||||||
|
400,
|
||||||
|
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
||||||
|
)
|
||||||
|
else:
|
||||||
rate = bigchaindb.config['statsd']['rate']
|
rate = bigchaindb.config['statsd']['rate']
|
||||||
with monitor.timer('write_transaction', rate=rate):
|
with monitor.timer('write_transaction', rate=rate):
|
||||||
bigchain.write_transaction(tx_obj)
|
bigchain.write_transaction(tx_obj)
|
||||||
else:
|
|
||||||
return make_error(400, 'Invalid transaction')
|
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ if [ "$WHAT_TO_DEPLOY" == "servers" ]; then
|
|||||||
# bigchaindb installed, so bigchaindb configure can't be called)
|
# bigchaindb installed, so bigchaindb configure can't be called)
|
||||||
|
|
||||||
# Transform the config files in the confiles directory
|
# Transform the config files in the confiles directory
|
||||||
# to have proper keyrings, api_endpoint values, etc.
|
# to have proper keyrings etc.
|
||||||
if [ "$USE_KEYPAIRS_FILE" == "True" ]; then
|
if [ "$USE_KEYPAIRS_FILE" == "True" ]; then
|
||||||
python clusterize_confiles.py -k confiles $NUM_NODES
|
python clusterize_confiles.py -k confiles $NUM_NODES
|
||||||
else
|
else
|
||||||
@ -184,8 +184,6 @@ if [ "$WHAT_TO_DEPLOY" == "servers" ]; then
|
|||||||
echo "To start BigchainDB on all the nodes, do: fab start_bigchaindb"
|
echo "To start BigchainDB on all the nodes, do: fab start_bigchaindb"
|
||||||
else
|
else
|
||||||
# Deploying clients
|
# Deploying clients
|
||||||
# The only thing to configure on clients is the api_endpoint
|
|
||||||
# It should be the public DNS name of a BigchainDB server
|
|
||||||
fab send_client_confile:client_confile
|
fab send_client_confile:client_confile
|
||||||
|
|
||||||
# Start sending load from the clients to the servers
|
# Start sending load from the clients to the servers
|
||||||
|
@ -98,9 +98,6 @@ for i, filename in enumerate(conf_files):
|
|||||||
# Allow incoming server traffic from any IP address
|
# Allow incoming server traffic from any IP address
|
||||||
# to port 9984
|
# to port 9984
|
||||||
conf_dict['server']['bind'] = '0.0.0.0:9984'
|
conf_dict['server']['bind'] = '0.0.0.0:9984'
|
||||||
# Set the api_endpoint
|
|
||||||
conf_dict['api_endpoint'] = 'http://' + public_dns_names[i] + \
|
|
||||||
':9984/api/v1'
|
|
||||||
|
|
||||||
# Delete the config file
|
# Delete the config file
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
|
@ -27,9 +27,9 @@ services:
|
|||||||
- ./setup.py:/usr/src/app/setup.py
|
- ./setup.py:/usr/src/app/setup.py
|
||||||
- ./setup.cfg:/usr/src/app/setup.cfg
|
- ./setup.cfg:/usr/src/app/setup.cfg
|
||||||
- ./pytest.ini:/usr/src/app/pytest.ini
|
- ./pytest.ini:/usr/src/app/pytest.ini
|
||||||
|
- ./tox.ini:/usr/src/app/tox.ini
|
||||||
environment:
|
environment:
|
||||||
BIGCHAINDB_DATABASE_HOST: rdb
|
BIGCHAINDB_DATABASE_HOST: rdb
|
||||||
BIGCHAINDB_API_ENDPOINT: http://bdb:9984/api/v1
|
|
||||||
BIGCHAINDB_SERVER_BIND: 0.0.0.0:9984
|
BIGCHAINDB_SERVER_BIND: 0.0.0.0:9984
|
||||||
ports:
|
ports:
|
||||||
- "9984"
|
- "9984"
|
||||||
|
2
docs.yml
2
docs.yml
@ -7,7 +7,7 @@ services:
|
|||||||
dockerfile: ./Dockerfile-dev
|
dockerfile: ./Dockerfile-dev
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/app/
|
- .:/usr/src/app/
|
||||||
command: make -C docs html
|
command: make -C docs/server html
|
||||||
vdocs:
|
vdocs:
|
||||||
image: nginx
|
image: nginx
|
||||||
ports:
|
ports:
|
||||||
|
0
docs/generate/__init__.py
Normal file
0
docs/generate/__init__.py
Normal file
@ -162,7 +162,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = []
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
|
89
docs/server/generate_http_server_api_documentation.py
Normal file
89
docs/server/generate_http_server_api_documentation.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
""" Script to build http examples for http server api docs """
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from bigchaindb.common.transaction import Asset, Transaction
|
||||||
|
|
||||||
|
|
||||||
|
TPLS = {}
|
||||||
|
|
||||||
|
TPLS['post-tx-request'] = """\
|
||||||
|
POST /transactions/ HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
%(tx)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPLS['post-tx-response'] = """\
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
%(tx)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPLS['get-tx-status-request'] = """\
|
||||||
|
GET /transactions/%(txid)s/status HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPLS['get-tx-status-response'] = """\
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "valid"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPLS['get-tx-request'] = """\
|
||||||
|
GET /transactions/%(txid)s HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
TPLS['get-tx-response'] = """\
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
%(tx)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
""" Main function """
|
||||||
|
privkey = 'CfdqtD7sS7FgkMoGPXw55MVGGFwQLAoHYTcBhZDtF99Z'
|
||||||
|
pubkey = '4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD'
|
||||||
|
asset = Asset(None, 'e6969f87-4fc9-4467-b62a-f0dfa1c85002')
|
||||||
|
tx = Transaction.create([pubkey], [([pubkey], 1)], asset=asset)
|
||||||
|
tx = tx.sign([privkey])
|
||||||
|
tx_json = json.dumps(tx.to_dict(), indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
base_path = os.path.join(os.path.dirname(__file__),
|
||||||
|
'source/drivers-clients/samples')
|
||||||
|
|
||||||
|
if not os.path.exists(base_path):
|
||||||
|
os.makedirs(base_path)
|
||||||
|
|
||||||
|
for name, tpl in TPLS.items():
|
||||||
|
path = os.path.join(base_path, name + '.http')
|
||||||
|
code = tpl % {'tx': tx_json, 'txid': tx.id}
|
||||||
|
with open(path, 'w') as handle:
|
||||||
|
handle.write(code)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(*_):
|
||||||
|
""" Fool sphinx into think it's an extension muahaha """
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -27,8 +27,6 @@ Transaction Schema
|
|||||||
|
|
||||||
* `Transaction`_
|
* `Transaction`_
|
||||||
|
|
||||||
* `Transaction Body`_
|
|
||||||
|
|
||||||
* Condition_
|
* Condition_
|
||||||
|
|
||||||
* Fulfillment_
|
* Fulfillment_
|
||||||
@ -58,11 +56,6 @@ Transaction Schema
|
|||||||
Transaction
|
Transaction
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
%(wrapper)s
|
|
||||||
|
|
||||||
Transaction Body
|
|
||||||
----------------
|
|
||||||
|
|
||||||
%(transaction)s
|
%(transaction)s
|
||||||
|
|
||||||
Condition
|
Condition
|
||||||
@ -158,9 +151,7 @@ def main():
|
|||||||
""" Main function """
|
""" Main function """
|
||||||
defs = TX_SCHEMA['definitions']
|
defs = TX_SCHEMA['definitions']
|
||||||
doc = TPL_DOC % {
|
doc = TPL_DOC % {
|
||||||
'wrapper': render_section('Transaction', TX_SCHEMA),
|
'transaction': render_section('Transaction', TX_SCHEMA),
|
||||||
'transaction': render_section('Transaction',
|
|
||||||
TX_SCHEMA['properties']['transaction']),
|
|
||||||
'condition': render_section('Condition', defs['condition']),
|
'condition': render_section('Condition', defs['condition']),
|
||||||
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
|
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
|
||||||
'asset': render_section('Asset', defs['asset']),
|
'asset': render_section('Asset', defs['asset']),
|
||||||
@ -168,12 +159,20 @@ def main():
|
|||||||
'file': os.path.basename(__file__),
|
'file': os.path.basename(__file__),
|
||||||
}
|
}
|
||||||
|
|
||||||
path = os.path.join(os.path.dirname(__file__),
|
base_path = os.path.join(os.path.dirname(__file__), 'source/schema')
|
||||||
'source/schema/transaction.rst')
|
path = os.path.join(base_path, 'transaction.rst')
|
||||||
|
|
||||||
|
if not os.path.exists(base_path):
|
||||||
|
os.makedirs(base_path)
|
||||||
|
|
||||||
with open(path, 'w') as handle:
|
with open(path, 'w') as handle:
|
||||||
handle.write(doc)
|
handle.write(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(*_):
|
||||||
|
""" Fool sphinx into think it's an extension muahaha """
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -3,4 +3,3 @@ Consensus
|
|||||||
#########
|
#########
|
||||||
|
|
||||||
.. automodule:: bigchaindb.consensus
|
.. automodule:: bigchaindb.consensus
|
||||||
:members:
|
|
||||||
|
@ -6,32 +6,27 @@ Block Creation
|
|||||||
==============
|
==============
|
||||||
|
|
||||||
.. automodule:: bigchaindb.pipelines.block
|
.. automodule:: bigchaindb.pipelines.block
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Block Voting
|
Block Voting
|
||||||
============
|
============
|
||||||
|
|
||||||
.. automodule:: bigchaindb.pipelines.vote
|
.. automodule:: bigchaindb.pipelines.vote
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Block Status
|
Block Status
|
||||||
============
|
============
|
||||||
|
|
||||||
.. automodule:: bigchaindb.pipelines.election
|
.. automodule:: bigchaindb.pipelines.election
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Stale Transaction Monitoring
|
Stale Transaction Monitoring
|
||||||
============================
|
============================
|
||||||
|
|
||||||
.. automodule:: bigchaindb.pipelines.stale
|
.. automodule:: bigchaindb.pipelines.stale
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Utilities
|
Utilities
|
||||||
=========
|
=========
|
||||||
|
|
||||||
.. automodule:: bigchaindb.pipelines.utils
|
.. automodule:: bigchaindb.pipelines.utils
|
||||||
:members:
|
|
||||||
|
@ -5,6 +5,5 @@ The Bigchain class
|
|||||||
The Bigchain class is the top-level Python API for BigchainDB. If you want to create and initialize a BigchainDB database, you create a Bigchain instance (object). Then you can use its various methods to create transactions, write transactions (to the object/database), read transactions, etc.
|
The Bigchain class is the top-level Python API for BigchainDB. If you want to create and initialize a BigchainDB database, you create a Bigchain instance (object). Then you can use its various methods to create transactions, write transactions (to the object/database), read transactions, etc.
|
||||||
|
|
||||||
.. autoclass:: bigchaindb.Bigchain
|
.. autoclass:: bigchaindb.Bigchain
|
||||||
:members:
|
|
||||||
|
|
||||||
.. automethod:: bigchaindb.core.Bigchain.__init__
|
.. automethod:: bigchaindb.core.Bigchain.__init__
|
||||||
|
@ -35,6 +35,10 @@ _version = {}
|
|||||||
with open('../../../bigchaindb/version.py') as fp:
|
with open('../../../bigchaindb/version.py') as fp:
|
||||||
exec(fp.read(), _version)
|
exec(fp.read(), _version)
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..'))
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
@ -44,10 +48,17 @@ extensions = [
|
|||||||
'sphinx.ext.napoleon',
|
'sphinx.ext.napoleon',
|
||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.httpdomain',
|
||||||
'sphinx.ext.autosectionlabel',
|
'sphinx.ext.autosectionlabel',
|
||||||
|
# Below are actually build steps made to look like sphinx extensions.
|
||||||
|
# It was the easiest way to get it running with ReadTheDocs.
|
||||||
|
'generate_schema_documentation',
|
||||||
|
'generate_http_server_api_documentation',
|
||||||
]
|
]
|
||||||
|
|
||||||
# autodoc settings
|
# autodoc settings
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
|
autodoc_default_flags = [
|
||||||
|
'members',
|
||||||
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -22,38 +22,30 @@ A transaction has the following structure:
|
|||||||
{
|
{
|
||||||
"id": "<hash of transaction, excluding signatures (see explanation)>",
|
"id": "<hash of transaction, excluding signatures (see explanation)>",
|
||||||
"version": "<version number of the transaction model>",
|
"version": "<version number of the transaction model>",
|
||||||
"transaction": {
|
"fulfillments": ["<list of fulfillments>"],
|
||||||
"fulfillments": ["<list of fulfillments>"],
|
"conditions": ["<list of conditions>"],
|
||||||
"conditions": ["<list of conditions>"],
|
"operation": "<string>",
|
||||||
"operation": "<string>",
|
"asset": "<digital asset description (explained in the next section)>",
|
||||||
"asset": "<digital asset description (explained in the next section)>",
|
"metadata": "<any JSON document>"
|
||||||
"metadata": {
|
|
||||||
"id": "<uuid>",
|
|
||||||
"data": "<any JSON document>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Here's some explanation of the contents of a :ref:`transaction <transaction>`:
|
Here's some explanation of the contents of a :ref:`transaction <transaction>`:
|
||||||
|
|
||||||
- :ref:`id <transaction.id>`: The id of the transaction, and also the database primary key.
|
- id: The :ref:`id <transaction.id>` of the transaction, and also the database primary key.
|
||||||
- :ref:`version <transaction.version>`: Version number of the transaction model, so that software can support different transaction models.
|
- version: :ref:`Version <transaction.version>` number of the transaction model, so that software can support different transaction models.
|
||||||
- :ref:`transaction <Transaction Body>`:
|
- **fulfillments**: List of fulfillments. Each :ref:`fulfillment <Fulfillment>` contains a pointer to an unspent asset
|
||||||
- **fulfillments**: List of fulfillments. Each :ref:`fulfillment <Fulfillment>` contains a pointer to an unspent asset
|
and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment*
|
||||||
and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment*
|
is usually a signature proving the ownership of the asset.
|
||||||
is usually a signature proving the ownership of the asset.
|
See :doc:`./crypto-conditions`.
|
||||||
See :doc:`./crypto-conditions`.
|
|
||||||
|
|
||||||
- **conditions**: List of conditions. Each :ref:`condition <Condition>` is a *crypto-condition* that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners.
|
- **conditions**: List of conditions. Each :ref:`condition <Condition>` is a *crypto-condition* that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners.
|
||||||
See :doc:`./crypto-conditions`.
|
See :doc:`./crypto-conditions`.
|
||||||
|
|
||||||
- **operation**: String representation of the :ref:`operation <transaction.operation>` being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated.
|
- **operation**: String representation of the :ref:`operation <transaction.operation>` being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated.
|
||||||
|
|
||||||
- **asset**: Definition of the digital :ref:`asset <Asset>`. See next section.
|
- **asset**: Definition of the digital :ref:`asset <Asset>`. See next section.
|
||||||
|
|
||||||
- **metadata**:
|
- **metadata**: User-provided transaction :ref:`metadata <metadata>`: Can be any JSON document, or `NULL`.
|
||||||
- :ref:`id <metadata.id>`: UUID version 4 (random) converted to a string of hex digits in standard form.
|
|
||||||
- :ref:`data <metadata.data>`: Can be any JSON document. It may be empty in the case of a transfer 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 whoever created it. A transfer transaction is signed by whoever currently controls or owns it.
|
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 whoever created it. A transfer transaction is signed by whoever currently controls or owns it.
|
||||||
|
|
||||||
|
@ -91,7 +91,6 @@ should give you something like:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
{
|
{
|
||||||
"api_endpoint": "http://bdb:9984/api/v1",
|
|
||||||
"keyring": [],
|
"keyring": [],
|
||||||
"public_key": "Brx8g4DdtEhccsENzNNV6yvQHR8s9ebhKyXPFkWUXh5e",
|
"public_key": "Brx8g4DdtEhccsENzNNV6yvQHR8s9ebhKyXPFkWUXh5e",
|
||||||
"software": "BigchainDB",
|
"software": "BigchainDB",
|
||||||
|
@ -26,17 +26,18 @@ details, see the "server" settings ("bind", "workers" and "threads") in
|
|||||||
<../server-reference/configuration>`.
|
<../server-reference/configuration>`.
|
||||||
|
|
||||||
|
|
||||||
API Root
|
API Root URL
|
||||||
--------
|
------------
|
||||||
|
|
||||||
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no
|
If you send an HTTP GET request to e.g. ``http://localhost:9984``
|
||||||
``/api/v1/`` on the end), then you should get an HTTP response with something
|
or ``http://apihosting4u.net:9984``
|
||||||
like the following in the body:
|
(with no ``/api/v1/`` on the end),
|
||||||
|
then you should get an HTTP response
|
||||||
|
with something like the following in the body:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"api_endpoint": "http://localhost:9984/api/v1",
|
|
||||||
"keyring": [
|
"keyring": [
|
||||||
"6qHyZew94NMmUTYyHnkZsB8cxJYuRNEiEpXHe1ih9QX3",
|
"6qHyZew94NMmUTYyHnkZsB8cxJYuRNEiEpXHe1ih9QX3",
|
||||||
"AdDuyrTyjrDt935YnFu4VBCVDhHtY2Y6rcy7x2TFeiRi"
|
"AdDuyrTyjrDt935YnFu4VBCVDhHtY2Y6rcy7x2TFeiRi"
|
||||||
@ -46,6 +47,25 @@ like the following in the body:
|
|||||||
"version": "0.6.0"
|
"version": "0.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
If the API endpoint is publicly-accessible,
|
||||||
|
then the public API Root URL is determined as follows:
|
||||||
|
|
||||||
|
- The public IP address (like 12.34.56.78)
|
||||||
|
is the public IP address of the machine exposing
|
||||||
|
the HTTP API to the public internet (e.g. either the machine hosting
|
||||||
|
Gunicorn or the machine running the reverse proxy such as Nginx).
|
||||||
|
It's determined by AWS, Azure, Rackspace, or whoever is hosting the machine.
|
||||||
|
|
||||||
|
- The DNS hostname (like apihosting4u.net) is determined by DNS records,
|
||||||
|
such as an "A Record" associating apihosting4u.net with 12.34.56.78
|
||||||
|
|
||||||
|
- The port (like 9984) is determined by the ``server.bind`` setting
|
||||||
|
if Gunicorn is exposed directly to the public Internet.
|
||||||
|
If a reverse proxy (like Nginx) is exposed directly to the public Internet
|
||||||
|
instead, then it could expose the HTTP API on whatever port it wants to.
|
||||||
|
(It should expose the HTTP API on port 9984, but it's not bound to do
|
||||||
|
that by anything other than convention.)
|
||||||
|
|
||||||
|
|
||||||
POST /transactions/
|
POST /transactions/
|
||||||
-------------------
|
-------------------
|
||||||
@ -54,113 +74,23 @@ POST /transactions/
|
|||||||
|
|
||||||
Push a new transaction.
|
Push a new transaction.
|
||||||
|
|
||||||
Note: The posted transaction should be valid `transaction
|
Note: The posted transaction should be a valid and signed `transaction
|
||||||
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
|
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
|
||||||
The steps to build a valid transaction are beyond the scope of this page.
|
The steps to build a valid transaction are beyond the scope of this page.
|
||||||
One would normally use a driver such as the `BigchainDB Python Driver
|
One would normally use a driver such as the `BigchainDB Python Driver
|
||||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
|
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
|
||||||
build a valid transaction.
|
build a valid transaction. The exact contents of a valid transaction depend
|
||||||
|
on the associated public/private keypairs.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/post-tx-request.http
|
||||||
|
:language: http
|
||||||
POST /transactions/ HTTP/1.1
|
|
||||||
Host: example.com
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"transaction": {
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"condition": {
|
|
||||||
"uri": "cc:4:20:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkk:96",
|
|
||||||
"details": {
|
|
||||||
"signature": null,
|
|
||||||
"type": "fulfillment",
|
|
||||||
"type_id": 4,
|
|
||||||
"bitmask": 32,
|
|
||||||
"public_key": "2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"amount": 1,
|
|
||||||
"owners_after": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"operation": "CREATE",
|
|
||||||
"asset": {
|
|
||||||
"divisible": false,
|
|
||||||
"updatable": false,
|
|
||||||
"data": null,
|
|
||||||
"id": "aebeab22-e672-4d3b-a187-bde5fda6533d",
|
|
||||||
"refillable": false
|
|
||||||
},
|
|
||||||
"metadata": null,
|
|
||||||
"fulfillments": [
|
|
||||||
{
|
|
||||||
"input": null,
|
|
||||||
"fulfillment": "cf:4:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkn2VnQaEWvecO1x82Qr2Va_JjFywLKIOEV1Ob9Ofkeln2K89ny2mB-s7RLNvYAVzWNiQnp18_nQEUsvwACEXTYJ",
|
|
||||||
"owners_before": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e",
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/post-tx-response.http
|
||||||
|
:language: http
|
||||||
HTTP/1.1 201 Created
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e",
|
|
||||||
"version": 1,
|
|
||||||
"transaction": {
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"amount": 1,
|
|
||||||
"condition": {
|
|
||||||
"uri": "cc:4:20:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkk:96",
|
|
||||||
"details": {
|
|
||||||
"signature": null,
|
|
||||||
"type_id": 4,
|
|
||||||
"type": "fulfillment",
|
|
||||||
"bitmask": 32,
|
|
||||||
"public_key": "2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"owners_after": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fulfillments": [
|
|
||||||
{
|
|
||||||
"input": null,
|
|
||||||
"fulfillment": "cf:4:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkn2VnQaEWvecO1x82Qr2Va_JjFywLKIOEV1Ob9Ofkeln2K89ny2mB-s7RLNvYAVzWNiQnp18_nQEUsvwACEXTYJ",
|
|
||||||
"owners_before": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"operation": "CREATE",
|
|
||||||
"asset": {
|
|
||||||
"updatable": false,
|
|
||||||
"refillable": false,
|
|
||||||
"divisible": false,
|
|
||||||
"data": null,
|
|
||||||
"id": "aebeab22-e672-4d3b-a187-bde5fda6533d"
|
|
||||||
},
|
|
||||||
"metadata": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:statuscode 201: A new transaction was created.
|
:statuscode 201: A new transaction was created.
|
||||||
:statuscode 400: The transaction was invalid and not created.
|
:statuscode 400: The transaction was invalid and not created.
|
||||||
@ -182,21 +112,13 @@ GET /transactions/{tx_id}/status
|
|||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/get-tx-status-request.http
|
||||||
|
:language: http
|
||||||
GET /transactions/7ad5a4b83bc8c70c4fd7420ff3c60693ab8e6d0e3124378ca69ed5acd2578792/status HTTP/1.1
|
|
||||||
Host: example.com
|
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/get-tx-status-response.http
|
||||||
|
:language: http
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "valid"
|
|
||||||
}
|
|
||||||
|
|
||||||
:statuscode 200: A transaction with that ID was found and the status is returned.
|
:statuscode 200: A transaction with that ID was found and the status is returned.
|
||||||
:statuscode 404: A transaction with that ID was not found.
|
:statuscode 404: A transaction with that ID was not found.
|
||||||
@ -217,60 +139,13 @@ GET /transactions/{tx_id}
|
|||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/get-tx-request.http
|
||||||
|
:language: http
|
||||||
GET /transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e HTTP/1.1
|
|
||||||
Host: example.com
|
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. literalinclude:: samples/get-tx-response.http
|
||||||
|
:language: http
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"transaction": {
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"condition": {
|
|
||||||
"uri": "cc:4:20:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkk:96",
|
|
||||||
"details": {
|
|
||||||
"signature": null,
|
|
||||||
"type": "fulfillment",
|
|
||||||
"type_id": 4,
|
|
||||||
"bitmask": 32,
|
|
||||||
"public_key": "2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"amount": 1,
|
|
||||||
"owners_after": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"operation": "CREATE",
|
|
||||||
"asset": {
|
|
||||||
"divisible": false,
|
|
||||||
"updatable": false,
|
|
||||||
"data": null,
|
|
||||||
"id": "aebeab22-e672-4d3b-a187-bde5fda6533d",
|
|
||||||
"refillable": false
|
|
||||||
},
|
|
||||||
"metadata": null,
|
|
||||||
"fulfillments": [
|
|
||||||
{
|
|
||||||
"input": null,
|
|
||||||
"fulfillment": "cf:4:GG-pi3CeIlySZhQoJVBh9O23PzrOuhnYI7OHqIbHjkn2VnQaEWvecO1x82Qr2Va_JjFywLKIOEV1Ob9Ofkeln2K89ny2mB-s7RLNvYAVzWNiQnp18_nQEUsvwACEXTYJ",
|
|
||||||
"owners_before": [
|
|
||||||
"2ePYHfV3yS3xTxF9EE3Xjo8zPwq2RmLPFAJGQqQKc3j6"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e",
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
:statuscode 200: A transaction with that ID was found.
|
:statuscode 200: A transaction with that ID was found.
|
||||||
:statuscode 404: A transaction with that ID was not found.
|
:statuscode 404: A transaction with that ID was not found.
|
||||||
@ -315,8 +190,8 @@ GET /unspents/
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
[
|
[
|
||||||
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0',
|
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0",
|
||||||
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1'
|
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1"
|
||||||
]
|
]
|
||||||
|
|
||||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||||
|
@ -167,7 +167,6 @@ Edit the created config file:
|
|||||||
|
|
||||||
* Open `$HOME/.bigchaindb` (the created config file) in your text editor.
|
* Open `$HOME/.bigchaindb` (the created config file) in your text editor.
|
||||||
* Change `"server": {"bind": "localhost:9984", ... }` to `"server": {"bind": "0.0.0.0:9984", ... }`. This makes it so traffic can come from any IP address to port 9984 (the HTTP Client-Server API port).
|
* Change `"server": {"bind": "localhost:9984", ... }` to `"server": {"bind": "0.0.0.0:9984", ... }`. This makes it so traffic can come from any IP address to port 9984 (the HTTP Client-Server API port).
|
||||||
* Change `"api_endpoint": "http://localhost:9984/api/v1"` to `"api_endpoint": "http://your_api_hostname:9984/api/v1"`
|
|
||||||
* Change `"keyring": []` to `"keyring": ["public_key_of_other_node_A", "public_key_of_other_node_B", "..."]` i.e. a list of the public keys of all the other nodes in the federation. The keyring should _not_ include your node's public key.
|
* Change `"keyring": []` to `"keyring": ["public_key_of_other_node_A", "public_key_of_other_node_B", "..."]` i.e. a list of the public keys of all the other nodes in the federation. The keyring should _not_ include your node's public key.
|
||||||
|
|
||||||
For more information about the BigchainDB config file, see [Configuring a BigchainDB Node](configuration.html).
|
For more information about the BigchainDB config file, see [Configuring a BigchainDB Node](configuration.html).
|
||||||
|
@ -1,307 +0,0 @@
|
|||||||
.. This file was auto generated by generate_schema_documentation.py
|
|
||||||
|
|
||||||
==================
|
|
||||||
Transaction Schema
|
|
||||||
==================
|
|
||||||
|
|
||||||
* `Transaction`_
|
|
||||||
|
|
||||||
* `Transaction Body`_
|
|
||||||
|
|
||||||
* Condition_
|
|
||||||
|
|
||||||
* Fulfillment_
|
|
||||||
|
|
||||||
* Asset_
|
|
||||||
|
|
||||||
* Metadata_
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#transaction-schema h2 {
|
|
||||||
border-top: solid 3px #6ab0de;
|
|
||||||
background-color: #e7f2fa;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
#transaction-schema h3 {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-left: solid 3px #ccc;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 6px;
|
|
||||||
font-size: 100%;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
Transaction
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This is the outer transaction wrapper. It contains the ID, version and the body of the transaction, which is also called ``transaction``.
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.id
|
|
||||||
^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** string
|
|
||||||
|
|
||||||
A sha3 digest of the transaction. The ID is calculated by removing all
|
|
||||||
derived hashes and signatures from the transaction, serializing it to
|
|
||||||
JSON with keys in sorted order and then hashing the resulting string
|
|
||||||
with sha3.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.transaction
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object
|
|
||||||
|
|
||||||
See: `Transaction Body`_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.version
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** integer
|
|
||||||
|
|
||||||
BigchainDB transaction schema version.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction Body
|
|
||||||
----------------
|
|
||||||
|
|
||||||
See: `Transaction Body`_.
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.operation
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** string
|
|
||||||
|
|
||||||
Type of the transaction:
|
|
||||||
|
|
||||||
A ``CREATE`` transaction creates an asset in BigchainDB. This
|
|
||||||
transaction has outputs (conditions) but no inputs (fulfillments),
|
|
||||||
so a dummy fulfillment is used.
|
|
||||||
|
|
||||||
A ``TRANSFER`` transaction transfers ownership of an asset, by providing
|
|
||||||
fulfillments to conditions of earlier transactions.
|
|
||||||
|
|
||||||
A ``GENESIS`` transaction is a special case transaction used as the
|
|
||||||
sole member of the first block in a BigchainDB ledger.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.asset
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object
|
|
||||||
|
|
||||||
Description of the asset being transacted.
|
|
||||||
|
|
||||||
See: `Asset`_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.fulfillments
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** array (object)
|
|
||||||
|
|
||||||
Array of the fulfillments (inputs) of a transaction.
|
|
||||||
|
|
||||||
See: Fulfillment_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.conditions
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** array (object)
|
|
||||||
|
|
||||||
Array of conditions (outputs) provided by this transaction.
|
|
||||||
|
|
||||||
See: Condition_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transaction.metadata
|
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object or null
|
|
||||||
|
|
||||||
User provided transaction metadata. This field may be ``null`` or may
|
|
||||||
contain an id and an object with freeform metadata.
|
|
||||||
|
|
||||||
See: `Metadata`_.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Condition
|
|
||||||
----------
|
|
||||||
|
|
||||||
An output of a transaction. A condition describes a quantity of an asset
|
|
||||||
and what conditions must be met in order for it to be fulfilled. See also:
|
|
||||||
fulfillment_.
|
|
||||||
|
|
||||||
|
|
||||||
Condition.condition
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object
|
|
||||||
|
|
||||||
Body of the condition. Has the properties:
|
|
||||||
|
|
||||||
- **details**: Details of the condition.
|
|
||||||
- **uri**: Condition encoded as an ASCII string.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Condition.owners_after
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** array (string) or null
|
|
||||||
|
|
||||||
List of public keys associated with asset ownership at the time
|
|
||||||
of the transaction.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Condition.amount
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** integer
|
|
||||||
|
|
||||||
Integral amount of the asset represented by this condition.
|
|
||||||
In the case of a non divisible asset, this will always be 1.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Fulfillment
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A fulfillment is an input to a transaction, named as such because it fulfills a condition of a previous transaction. In the case of a ``CREATE`` transaction, a fulfillment may provide no ``input``.
|
|
||||||
|
|
||||||
Fulfillment.owners_before
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** array (string) or null
|
|
||||||
|
|
||||||
List of public keys of the previous owners of the asset.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Fulfillment.fulfillment
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object or string
|
|
||||||
|
|
||||||
Fulfillment of a condition_, or put a different way, this is a
|
|
||||||
payload that satisfies a condition in order to spend the associated
|
|
||||||
asset.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Fulfillment.input
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object or null
|
|
||||||
|
|
||||||
Reference to a condition of a previous transaction
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Asset
|
|
||||||
-----
|
|
||||||
|
|
||||||
Description of the asset being transacted. In the case of a ``TRANSFER``
|
|
||||||
transaction, this field contains only the ID of asset. In the case
|
|
||||||
of a ``CREATE`` transaction, this field may contain properties:
|
|
||||||
|
|
||||||
|
|
||||||
Asset.id
|
|
||||||
^^^^^^^^
|
|
||||||
|
|
||||||
**type:** string
|
|
||||||
|
|
||||||
A `UUID <https://tools.ietf.org/html/rfc4122.html>`_
|
|
||||||
of type 4 (random).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Asset.divisible
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** boolean
|
|
||||||
|
|
||||||
Whether or not the asset has a quantity that may be partially spent.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Asset.updatable
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** boolean
|
|
||||||
|
|
||||||
Whether or not the description of the asset may be updated. Defaults to false.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Asset.refillable
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** boolean
|
|
||||||
|
|
||||||
Whether the amount of the asset can change after its creation. Defaults to false.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Asset.data
|
|
||||||
^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object or null
|
|
||||||
|
|
||||||
User provided metadata associated with the asset. May also be ``null``.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Metadata
|
|
||||||
--------
|
|
||||||
|
|
||||||
User provided transaction metadata. This field may be ``null`` or may
|
|
||||||
contain an id and an object with freeform metadata.
|
|
||||||
|
|
||||||
|
|
||||||
Metadata.id
|
|
||||||
^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** string
|
|
||||||
|
|
||||||
A `UUID <https://tools.ietf.org/html/rfc4122.html>`_
|
|
||||||
of type 4 (random).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Metadata.data
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
**type:** object
|
|
||||||
|
|
||||||
User provided transaction metadata.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,11 +17,11 @@ For convenience, here's a list of all the relevant environment variables (docume
|
|||||||
`BIGCHAINDB_SERVER_BIND`<br>
|
`BIGCHAINDB_SERVER_BIND`<br>
|
||||||
`BIGCHAINDB_SERVER_WORKERS`<br>
|
`BIGCHAINDB_SERVER_WORKERS`<br>
|
||||||
`BIGCHAINDB_SERVER_THREADS`<br>
|
`BIGCHAINDB_SERVER_THREADS`<br>
|
||||||
`BIGCHAINDB_API_ENDPOINT`<br>
|
|
||||||
`BIGCHAINDB_STATSD_HOST`<br>
|
`BIGCHAINDB_STATSD_HOST`<br>
|
||||||
`BIGCHAINDB_STATSD_PORT`<br>
|
`BIGCHAINDB_STATSD_PORT`<br>
|
||||||
`BIGCHAINDB_STATSD_RATE`<br>
|
`BIGCHAINDB_STATSD_RATE`<br>
|
||||||
`BIGCHAINDB_CONFIG_PATH`<br>
|
`BIGCHAINDB_CONFIG_PATH`<br>
|
||||||
|
`BIGCHAINDB_BACKLOG_REASSIGN_DELAY`<br>
|
||||||
|
|
||||||
The local config file is `$HOME/.bigchaindb` by default (a file which might not even exist), but you can tell BigchainDB to use a different file by using the `-c` command-line option, e.g. `bigchaindb -c path/to/config_file.json start`
|
The local config file is `$HOME/.bigchaindb` by default (a file which might not even exist), but you can tell BigchainDB to use a different file by using the `-c` command-line option, e.g. `bigchaindb -c path/to/config_file.json start`
|
||||||
or using the `BIGCHAINDB_CONFIG_PATH` environment variable, e.g. `BIGHAINDB_CONFIG_PATH=.my_bigchaindb_config bigchaindb start`.
|
or using the `BIGCHAINDB_CONFIG_PATH` environment variable, e.g. `BIGHAINDB_CONFIG_PATH=.my_bigchaindb_config bigchaindb start`.
|
||||||
@ -140,26 +140,6 @@ export BIGCHAINDB_SERVER_THREADS=5
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## api_endpoint
|
|
||||||
|
|
||||||
`api_endpoint` is the URL where a BigchainDB client can get access to the HTTP client-server API.
|
|
||||||
|
|
||||||
**Example using an environment variable**
|
|
||||||
```text
|
|
||||||
export BIGCHAINDB_API_ENDPOINT="http://localhost:9984/api/v1"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example config file snippet**
|
|
||||||
```js
|
|
||||||
"api_endpoint": "http://webserver.blocks587.net:9984/api/v1"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Default value (from a config file)**
|
|
||||||
```js
|
|
||||||
"api_endpoint": "http://localhost:9984/api/v1"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## statsd.host, statsd.port & statsd.rate
|
## statsd.host, statsd.port & statsd.rate
|
||||||
|
|
||||||
These settings are used to configure where, and how often, [StatsD](https://github.com/etsy/statsd) should send data for [cluster monitoring](../clusters-feds/monitoring.html) purposes. `statsd.host` is the hostname of the monitoring server, where StatsD should send its data. `stats.port` is the port. `statsd.rate` is the fraction of transaction operations that should be sampled. It's a float between 0.0 and 1.0.
|
These settings are used to configure where, and how often, [StatsD](https://github.com/etsy/statsd) should send data for [cluster monitoring](../clusters-feds/monitoring.html) purposes. `statsd.host` is the hostname of the monitoring server, where StatsD should send its data. `stats.port` is the port. `statsd.rate` is the fraction of transaction operations that should be sampled. It's a float between 0.0 and 1.0.
|
||||||
@ -175,3 +155,17 @@ export BIGCHAINDB_STATSD_RATE=0.01
|
|||||||
```js
|
```js
|
||||||
"statsd": {"host": "localhost", "port": 8125, "rate": 0.01}
|
"statsd": {"host": "localhost", "port": 8125, "rate": 0.01}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## backlog_reassign_delay
|
||||||
|
|
||||||
|
Specifies how long, in seconds, transactions can remain in the backlog before being reassigned. Long-waiting transactions must be reassigned because the assigned node may no longer be responsive. The default duration is 120 seconds.
|
||||||
|
|
||||||
|
**Example using environment variables**
|
||||||
|
```text
|
||||||
|
export BIGCHAINDB_BACKLOG_REASSIGN_DELAY=30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Default value (from a config file)**
|
||||||
|
```js
|
||||||
|
"backlog_reassign_delay": 120
|
||||||
|
```
|
||||||
|
24
setup.py
24
setup.py
@ -27,18 +27,6 @@ def check_setuptools_features():
|
|||||||
|
|
||||||
check_setuptools_features()
|
check_setuptools_features()
|
||||||
|
|
||||||
|
|
||||||
tests_require = [
|
|
||||||
'coverage',
|
|
||||||
'pep8',
|
|
||||||
'flake8',
|
|
||||||
'pylint',
|
|
||||||
'pytest',
|
|
||||||
'pytest-cov>=2.2.1',
|
|
||||||
'pytest-xdist',
|
|
||||||
'pytest-flask',
|
|
||||||
]
|
|
||||||
|
|
||||||
dev_require = [
|
dev_require = [
|
||||||
'ipdb',
|
'ipdb',
|
||||||
'ipython',
|
'ipython',
|
||||||
@ -52,6 +40,18 @@ docs_require = [
|
|||||||
'sphinxcontrib-napoleon>=0.4.4',
|
'sphinxcontrib-napoleon>=0.4.4',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
tests_require = [
|
||||||
|
'coverage',
|
||||||
|
'pep8',
|
||||||
|
'flake8',
|
||||||
|
'pylint',
|
||||||
|
'pytest>=3.0.0',
|
||||||
|
'pytest-cov>=2.2.1',
|
||||||
|
'pytest-xdist',
|
||||||
|
'pytest-flask',
|
||||||
|
'tox',
|
||||||
|
] + docs_require
|
||||||
|
|
||||||
benchmarks_require = [
|
benchmarks_require = [
|
||||||
'line-profiler==1.0',
|
'line-profiler==1.0',
|
||||||
]
|
]
|
||||||
|
@ -136,12 +136,6 @@ def uuid4():
|
|||||||
return UUID4
|
return UUID4
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def metadata(data, data_id):
|
|
||||||
from bigchaindb.common.transaction import Metadata
|
|
||||||
return Metadata(data, data_id)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def utx(user_ffill, user_cond):
|
def utx(user_ffill, user_cond):
|
||||||
from bigchaindb.common.transaction import Transaction, Asset
|
from bigchaindb.common.transaction import Transaction, Asset
|
||||||
|
@ -16,6 +16,16 @@ def test_validate_transaction_signed_transfer(signed_transfer_tx):
|
|||||||
validate_transaction_schema(signed_transfer_tx.to_dict())
|
validate_transaction_schema(signed_transfer_tx.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_fails_metadata_empty_dict(create_tx):
|
||||||
|
create_tx.metadata = {'a': 1}
|
||||||
|
validate_transaction_schema(create_tx.to_dict())
|
||||||
|
create_tx.metadata = None
|
||||||
|
validate_transaction_schema(create_tx.to_dict())
|
||||||
|
create_tx.metadata = {}
|
||||||
|
with raises(SchemaValidationError):
|
||||||
|
validate_transaction_schema(create_tx.to_dict())
|
||||||
|
|
||||||
|
|
||||||
def test_validation_fails():
|
def test_validation_fails():
|
||||||
with raises(SchemaValidationError):
|
with raises(SchemaValidationError):
|
||||||
validate_transaction_schema({})
|
validate_transaction_schema({})
|
||||||
|
@ -301,20 +301,18 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
|
|||||||
expected = {
|
expected = {
|
||||||
'id': tx_id,
|
'id': tx_id,
|
||||||
'version': Transaction.VERSION,
|
'version': Transaction.VERSION,
|
||||||
'transaction': {
|
# NOTE: This test assumes that Fulfillments and Conditions can
|
||||||
# NOTE: This test assumes that Fulfillments and Conditions can
|
# successfully be serialized
|
||||||
# successfully be serialized
|
'fulfillments': [user_ffill.to_dict()],
|
||||||
'fulfillments': [user_ffill.to_dict()],
|
'conditions': [user_cond.to_dict()],
|
||||||
'conditions': [user_cond.to_dict()],
|
'operation': Transaction.CREATE,
|
||||||
'operation': Transaction.CREATE,
|
'metadata': None,
|
||||||
'metadata': None,
|
'asset': {
|
||||||
'asset': {
|
'id': data_id,
|
||||||
'id': data_id,
|
'divisible': False,
|
||||||
'divisible': False,
|
'updatable': False,
|
||||||
'updatable': False,
|
'refillable': False,
|
||||||
'refillable': False,
|
'data': data,
|
||||||
'data': data,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +320,7 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
|
|||||||
[user_cond])
|
[user_cond])
|
||||||
tx_dict = tx.to_dict()
|
tx_dict = tx.to_dict()
|
||||||
tx_dict['id'] = tx_id
|
tx_dict['id'] = tx_id
|
||||||
tx_dict['transaction']['asset']['id'] = data_id
|
tx_dict['asset']['id'] = data_id
|
||||||
|
|
||||||
assert tx_dict == expected
|
assert tx_dict == expected
|
||||||
|
|
||||||
@ -342,20 +340,18 @@ def test_transaction_deserialization(user_ffill, user_cond, data, uuid4):
|
|||||||
|
|
||||||
tx = {
|
tx = {
|
||||||
'version': Transaction.VERSION,
|
'version': Transaction.VERSION,
|
||||||
'transaction': {
|
# NOTE: This test assumes that Fulfillments and Conditions can
|
||||||
# NOTE: This test assumes that Fulfillments and Conditions can
|
# successfully be serialized
|
||||||
# successfully be serialized
|
'fulfillments': [user_ffill.to_dict()],
|
||||||
'fulfillments': [user_ffill.to_dict()],
|
'conditions': [user_cond.to_dict()],
|
||||||
'conditions': [user_cond.to_dict()],
|
'operation': Transaction.CREATE,
|
||||||
'operation': Transaction.CREATE,
|
'metadata': None,
|
||||||
'metadata': None,
|
'asset': {
|
||||||
'asset': {
|
'id': uuid4,
|
||||||
'id': uuid4,
|
'divisible': False,
|
||||||
'divisible': False,
|
'updatable': False,
|
||||||
'updatable': False,
|
'refillable': False,
|
||||||
'refillable': False,
|
'data': data,
|
||||||
'data': data,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx_no_signatures = Transaction._remove_signatures(tx)
|
tx_no_signatures = Transaction._remove_signatures(tx)
|
||||||
@ -387,34 +383,6 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub):
|
|||||||
Fulfillment(user_ffill, [], tx_input='somethingthatiswrong')
|
Fulfillment(user_ffill, [], tx_input='somethingthatiswrong')
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_metadata_initialization():
|
|
||||||
from bigchaindb.common.transaction import Metadata
|
|
||||||
|
|
||||||
with raises(TypeError):
|
|
||||||
Metadata([])
|
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_serialization(data, data_id):
|
|
||||||
from bigchaindb.common.transaction import Metadata
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
'data': data,
|
|
||||||
'id': data_id,
|
|
||||||
}
|
|
||||||
metadata = Metadata(data, data_id)
|
|
||||||
|
|
||||||
assert metadata.to_dict() == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_deserialization(data, data_id):
|
|
||||||
from bigchaindb.common.transaction import Metadata
|
|
||||||
|
|
||||||
expected = Metadata(data, data_id)
|
|
||||||
metadata = Metadata.from_dict({'data': data, 'id': data_id})
|
|
||||||
|
|
||||||
assert metadata == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_transaction_link_serialization():
|
def test_transaction_link_serialization():
|
||||||
from bigchaindb.common.transaction import TransactionLink
|
from bigchaindb.common.transaction import TransactionLink
|
||||||
|
|
||||||
@ -760,37 +728,32 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data, uuid4):
|
|||||||
from .util import validate_transaction_model
|
from .util import validate_transaction_model
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'transaction': {
|
'conditions': [user_cond.to_dict()],
|
||||||
'conditions': [user_cond.to_dict()],
|
'metadata': data,
|
||||||
'metadata': {
|
'asset': {
|
||||||
'data': data,
|
'id': uuid4,
|
||||||
},
|
'divisible': False,
|
||||||
'asset': {
|
'updatable': False,
|
||||||
'id': uuid4,
|
'refillable': False,
|
||||||
'divisible': False,
|
'data': data,
|
||||||
'updatable': False,
|
|
||||||
'refillable': False,
|
|
||||||
'data': data,
|
|
||||||
},
|
|
||||||
'fulfillments': [
|
|
||||||
{
|
|
||||||
'owners_before': [
|
|
||||||
user_pub
|
|
||||||
],
|
|
||||||
'fulfillment': None,
|
|
||||||
'input': None
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'operation': 'CREATE',
|
|
||||||
},
|
},
|
||||||
|
'fulfillments': [
|
||||||
|
{
|
||||||
|
'owners_before': [
|
||||||
|
user_pub
|
||||||
|
],
|
||||||
|
'fulfillment': None,
|
||||||
|
'input': None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'operation': 'CREATE',
|
||||||
'version': 1,
|
'version': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
asset = Asset(data, uuid4)
|
asset = Asset(data, uuid4)
|
||||||
tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset)
|
tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset)
|
||||||
tx_dict = tx.to_dict()
|
tx_dict = tx.to_dict()
|
||||||
tx_dict['transaction']['metadata'].pop('id')
|
tx_dict['fulfillments'][0]['fulfillment'] = None
|
||||||
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None
|
|
||||||
tx_dict.pop('id')
|
tx_dict.pop('id')
|
||||||
|
|
||||||
assert tx_dict == expected
|
assert tx_dict == expected
|
||||||
@ -815,16 +778,12 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
|
|||||||
# weight = len(owners_before)
|
# weight = len(owners_before)
|
||||||
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict()
|
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict()
|
||||||
expected = {
|
expected = {
|
||||||
'transaction': {
|
'conditions': [user_cond.to_dict(), user2_cond.to_dict()],
|
||||||
'conditions': [user_cond.to_dict(), user2_cond.to_dict()],
|
'metadata': {
|
||||||
'metadata': {
|
'message': 'hello'
|
||||||
'data': {
|
|
||||||
'message': 'hello'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'fulfillments': [ffill],
|
|
||||||
'operation': 'CREATE',
|
|
||||||
},
|
},
|
||||||
|
'fulfillments': [ffill],
|
||||||
|
'operation': 'CREATE',
|
||||||
'version': 1
|
'version': 1
|
||||||
}
|
}
|
||||||
asset = Asset(divisible=True)
|
asset = Asset(divisible=True)
|
||||||
@ -833,8 +792,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
|
|||||||
asset=asset,
|
asset=asset,
|
||||||
metadata={'message': 'hello'}).to_dict()
|
metadata={'message': 'hello'}).to_dict()
|
||||||
tx.pop('id')
|
tx.pop('id')
|
||||||
tx['transaction']['metadata'].pop('id')
|
tx.pop('asset')
|
||||||
tx['transaction'].pop('asset')
|
|
||||||
|
|
||||||
assert tx == expected
|
assert tx == expected
|
||||||
|
|
||||||
@ -861,29 +819,25 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
|
|||||||
from bigchaindb.common.transaction import Transaction, Asset
|
from bigchaindb.common.transaction import Transaction, Asset
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'transaction': {
|
'conditions': [user_user2_threshold_cond.to_dict()],
|
||||||
'conditions': [user_user2_threshold_cond.to_dict()],
|
'metadata': data,
|
||||||
'metadata': {
|
'asset': {
|
||||||
'data': data,
|
'id': uuid4,
|
||||||
},
|
'divisible': False,
|
||||||
'asset': {
|
'updatable': False,
|
||||||
'id': uuid4,
|
'refillable': False,
|
||||||
'divisible': False,
|
'data': data,
|
||||||
'updatable': False,
|
|
||||||
'refillable': False,
|
|
||||||
'data': data,
|
|
||||||
},
|
|
||||||
'fulfillments': [
|
|
||||||
{
|
|
||||||
'owners_before': [
|
|
||||||
user_pub,
|
|
||||||
],
|
|
||||||
'fulfillment': None,
|
|
||||||
'input': None
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'operation': 'CREATE',
|
|
||||||
},
|
},
|
||||||
|
'fulfillments': [
|
||||||
|
{
|
||||||
|
'owners_before': [
|
||||||
|
user_pub,
|
||||||
|
],
|
||||||
|
'fulfillment': None,
|
||||||
|
'input': None
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'operation': 'CREATE',
|
||||||
'version': 1
|
'version': 1
|
||||||
}
|
}
|
||||||
asset = Asset(data, uuid4)
|
asset = Asset(data, uuid4)
|
||||||
@ -891,8 +845,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
|
|||||||
data, asset)
|
data, asset)
|
||||||
tx_dict = tx.to_dict()
|
tx_dict = tx.to_dict()
|
||||||
tx_dict.pop('id')
|
tx_dict.pop('id')
|
||||||
tx_dict['transaction']['metadata'].pop('id')
|
tx_dict['fulfillments'][0]['fulfillment'] = None
|
||||||
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None
|
|
||||||
|
|
||||||
assert tx_dict == expected
|
assert tx_dict == expected
|
||||||
|
|
||||||
@ -946,26 +899,24 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
|||||||
from .util import validate_transaction_model
|
from .util import validate_transaction_model
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'transaction': {
|
'conditions': [user2_cond.to_dict()],
|
||||||
'conditions': [user2_cond.to_dict()],
|
'metadata': None,
|
||||||
'metadata': None,
|
'asset': {
|
||||||
'asset': {
|
'id': uuid4,
|
||||||
'id': uuid4,
|
|
||||||
},
|
|
||||||
'fulfillments': [
|
|
||||||
{
|
|
||||||
'owners_before': [
|
|
||||||
user_pub
|
|
||||||
],
|
|
||||||
'fulfillment': None,
|
|
||||||
'input': {
|
|
||||||
'txid': tx.id,
|
|
||||||
'cid': 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'operation': 'TRANSFER',
|
|
||||||
},
|
},
|
||||||
|
'fulfillments': [
|
||||||
|
{
|
||||||
|
'owners_before': [
|
||||||
|
user_pub
|
||||||
|
],
|
||||||
|
'fulfillment': None,
|
||||||
|
'input': {
|
||||||
|
'txid': tx.id,
|
||||||
|
'cid': 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'operation': 'TRANSFER',
|
||||||
'version': 1
|
'version': 1
|
||||||
}
|
}
|
||||||
inputs = tx.to_inputs([0])
|
inputs = tx.to_inputs([0])
|
||||||
@ -973,14 +924,13 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
|||||||
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset)
|
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset)
|
||||||
transfer_tx = transfer_tx.sign([user_priv])
|
transfer_tx = transfer_tx.sign([user_priv])
|
||||||
transfer_tx = transfer_tx.to_dict()
|
transfer_tx = transfer_tx.to_dict()
|
||||||
transfer_tx_body = transfer_tx['transaction']
|
|
||||||
|
|
||||||
expected_input = deepcopy(inputs[0])
|
expected_input = deepcopy(inputs[0])
|
||||||
expected['id'] = transfer_tx['id']
|
expected['id'] = transfer_tx['id']
|
||||||
expected_input.fulfillment.sign(serialize(expected).encode(),
|
expected_input.fulfillment.sign(serialize(expected).encode(),
|
||||||
PrivateKey(user_priv))
|
PrivateKey(user_priv))
|
||||||
expected_ffill = expected_input.fulfillment.serialize_uri()
|
expected_ffill = expected_input.fulfillment.serialize_uri()
|
||||||
transfer_ffill = transfer_tx_body['fulfillments'][0]['fulfillment']
|
transfer_ffill = transfer_tx['fulfillments'][0]['fulfillment']
|
||||||
|
|
||||||
assert transfer_ffill == expected_ffill
|
assert transfer_ffill == expected_ffill
|
||||||
|
|
||||||
@ -1001,32 +951,30 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
|||||||
tx = tx.sign([user_priv])
|
tx = tx.sign([user_priv])
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'transaction': {
|
'conditions': [user2_cond.to_dict(), user2_cond.to_dict()],
|
||||||
'conditions': [user2_cond.to_dict(), user2_cond.to_dict()],
|
'metadata': None,
|
||||||
'metadata': None,
|
'fulfillments': [
|
||||||
'fulfillments': [
|
{
|
||||||
{
|
'owners_before': [
|
||||||
'owners_before': [
|
user_pub
|
||||||
user_pub
|
],
|
||||||
],
|
'fulfillment': None,
|
||||||
'fulfillment': None,
|
'input': {
|
||||||
'input': {
|
'txid': tx.id,
|
||||||
'txid': tx.id,
|
'cid': 0
|
||||||
'cid': 0
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
'owners_before': [
|
|
||||||
user2_pub
|
|
||||||
],
|
|
||||||
'fulfillment': None,
|
|
||||||
'input': {
|
|
||||||
'txid': tx.id,
|
|
||||||
'cid': 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
}, {
|
||||||
'operation': 'TRANSFER',
|
'owners_before': [
|
||||||
},
|
user2_pub
|
||||||
|
],
|
||||||
|
'fulfillment': None,
|
||||||
|
'input': {
|
||||||
|
'txid': tx.id,
|
||||||
|
'cid': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'operation': 'TRANSFER',
|
||||||
'version': 1
|
'version': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1041,10 +989,10 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
|||||||
assert transfer_tx.fulfillments_valid(tx.conditions) is True
|
assert transfer_tx.fulfillments_valid(tx.conditions) is True
|
||||||
|
|
||||||
transfer_tx = transfer_tx.to_dict()
|
transfer_tx = transfer_tx.to_dict()
|
||||||
transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None
|
transfer_tx['fulfillments'][0]['fulfillment'] = None
|
||||||
transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None
|
transfer_tx['fulfillments'][1]['fulfillment'] = None
|
||||||
|
transfer_tx.pop('asset')
|
||||||
transfer_tx.pop('id')
|
transfer_tx.pop('id')
|
||||||
transfer_tx['transaction'].pop('asset')
|
|
||||||
|
|
||||||
assert expected == transfer_tx
|
assert expected == transfer_tx
|
||||||
|
|
||||||
|
@ -178,39 +178,6 @@ class TestBigchainApi(object):
|
|||||||
assert b.get_transaction(tx1.id) is None
|
assert b.get_transaction(tx1.id) is None
|
||||||
assert b.get_transaction(tx2.id) == tx2
|
assert b.get_transaction(tx2.id) == tx2
|
||||||
|
|
||||||
def test_get_transactions_for_metadata(self, b, user_pk):
|
|
||||||
from bigchaindb.models import Transaction
|
|
||||||
|
|
||||||
metadata = {'msg': 'Hello BigchainDB!'}
|
|
||||||
tx = Transaction.create([b.me], [([user_pk], 1)], metadata=metadata)
|
|
||||||
|
|
||||||
block = b.create_block([tx])
|
|
||||||
b.write_block(block, durability='hard')
|
|
||||||
|
|
||||||
matches = b.get_transaction_by_metadata_id(tx.metadata.data_id)
|
|
||||||
assert len(matches) == 1
|
|
||||||
assert matches[0].id == tx.id
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
|
||||||
def test_get_transactions_for_metadata_invalid_block(self, b, user_pk):
|
|
||||||
from bigchaindb.models import Transaction
|
|
||||||
|
|
||||||
metadata = {'msg': 'Hello BigchainDB!'}
|
|
||||||
tx = Transaction.create([b.me], [([user_pk], 1)], metadata=metadata)
|
|
||||||
|
|
||||||
block = b.create_block([tx])
|
|
||||||
b.write_block(block, durability='hard')
|
|
||||||
# vote block invalid
|
|
||||||
vote = b.vote(block.id, b.get_last_voted_block().id, False)
|
|
||||||
b.write_vote(vote)
|
|
||||||
|
|
||||||
matches = b.get_transaction_by_metadata_id(tx.metadata.data_id)
|
|
||||||
assert len(matches) == 0
|
|
||||||
|
|
||||||
def test_get_transactions_for_metadata_mismatch(self, b):
|
|
||||||
matches = b.get_transaction_by_metadata_id('missing')
|
|
||||||
assert not matches
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_write_transaction(self, b, user_pk, user_sk):
|
def test_write_transaction(self, b, user_pk, user_sk):
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
@ -306,8 +273,8 @@ class TestBigchainApi(object):
|
|||||||
block = b.backend.get_genesis_block()
|
block = b.backend.get_genesis_block()
|
||||||
|
|
||||||
assert len(block['block']['transactions']) == 1
|
assert len(block['block']['transactions']) == 1
|
||||||
assert block['block']['transactions'][0]['transaction']['operation'] == 'GENESIS'
|
assert block['block']['transactions'][0]['operation'] == 'GENESIS'
|
||||||
assert block['block']['transactions'][0]['transaction']['fulfillments'][0]['input'] is None
|
assert block['block']['transactions'][0]['fulfillments'][0]['input'] is None
|
||||||
|
|
||||||
def test_create_genesis_block_fails_if_table_not_empty(self, b):
|
def test_create_genesis_block_fails_if_table_not_empty(self, b):
|
||||||
from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError
|
from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError
|
||||||
@ -646,7 +613,7 @@ class TestTransactionValidation(object):
|
|||||||
|
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
signed_transfer_tx.metadata.data = {'different': 1}
|
signed_transfer_tx.metadata = {'different': 1}
|
||||||
# FIXME: https://github.com/bigchaindb/bigchaindb/issues/592
|
# FIXME: https://github.com/bigchaindb/bigchaindb/issues/592
|
||||||
with pytest.raises(DoubleSpend):
|
with pytest.raises(DoubleSpend):
|
||||||
b.validate_transaction(signed_transfer_tx)
|
b.validate_transaction(signed_transfer_tx)
|
||||||
|
@ -77,8 +77,6 @@ def test_create_bigchain_secondary_index():
|
|||||||
'block_timestamp').run(conn) is True
|
'block_timestamp').run(conn) is True
|
||||||
assert r.db(dbname).table('bigchain').index_list().contains(
|
assert r.db(dbname).table('bigchain').index_list().contains(
|
||||||
'transaction_id').run(conn) is True
|
'transaction_id').run(conn) is True
|
||||||
assert r.db(dbname).table('bigchain').index_list().contains(
|
|
||||||
'metadata_id').run(conn) is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_backlog_table():
|
def test_create_backlog_table():
|
||||||
|
0
tests/integration/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
35
tests/integration/conftest.py
Normal file
35
tests/integration/conftest.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import pytest
|
||||||
|
from bigchaindb.pipelines import block, election, vote, stale
|
||||||
|
|
||||||
|
# TODO: fix this import madness
|
||||||
|
from ..db import conftest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module', autouse=True)
|
||||||
|
def restore_config(request, node_config):
|
||||||
|
from bigchaindb import config_utils
|
||||||
|
config_utils.set_config(node_config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module', autouse=True)
|
||||||
|
def setup_database(request, node_config):
|
||||||
|
conftest.setup_database(request, node_config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function', autouse=True)
|
||||||
|
def cleanup_tables(request, node_config):
|
||||||
|
conftest.cleanup_tables(request, node_config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def processes(b):
|
||||||
|
b.create_genesis_block()
|
||||||
|
block_maker = block.start()
|
||||||
|
voter = vote.start()
|
||||||
|
election_runner = election.start()
|
||||||
|
stale_monitor = stale.start()
|
||||||
|
yield
|
||||||
|
block_maker.terminate()
|
||||||
|
voter.terminate()
|
||||||
|
election_runner.terminate()
|
||||||
|
stale_monitor.terminate()
|
66
tests/integration/test_integration.py
Normal file
66
tests/integration/test_integration.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import time
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bigchaindb import Bigchain
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def inputs(user_pk):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
|
||||||
|
b = Bigchain()
|
||||||
|
|
||||||
|
# create blocks with transactions for `USER` to spend
|
||||||
|
for block in range(4):
|
||||||
|
transactions = [
|
||||||
|
Transaction.create(
|
||||||
|
[b.me], [([user_pk], 1)],
|
||||||
|
metadata={'i': i})
|
||||||
|
.sign([b.me_private])
|
||||||
|
for i in range(10)
|
||||||
|
]
|
||||||
|
block = b.create_block(transactions)
|
||||||
|
b.write_block(block, durability='hard')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('processes')
|
||||||
|
def test_fast_double_create(b, user_pk):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||||
|
metadata={'test': 'test'}) \
|
||||||
|
.sign([b.me_private])
|
||||||
|
|
||||||
|
# write everything fast
|
||||||
|
b.write_transaction(tx)
|
||||||
|
b.write_transaction(tx)
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
tx_returned = b.get_transaction(tx.id)
|
||||||
|
|
||||||
|
# test that the tx can be queried
|
||||||
|
assert tx_returned == tx
|
||||||
|
# test the transaction appears only once
|
||||||
|
last_voted_block = b.get_last_voted_block()
|
||||||
|
assert len(last_voted_block.transactions) == 1
|
||||||
|
assert b.backend.count_blocks() == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('processes')
|
||||||
|
def test_double_create(b, user_pk):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||||
|
metadata={'test': 'test'}) \
|
||||||
|
.sign([b.me_private])
|
||||||
|
|
||||||
|
b.write_transaction(tx)
|
||||||
|
time.sleep(2)
|
||||||
|
b.write_transaction(tx)
|
||||||
|
time.sleep(2)
|
||||||
|
tx_returned = b.get_transaction(tx.id)
|
||||||
|
|
||||||
|
# test that the tx can be queried
|
||||||
|
assert tx_returned == tx
|
||||||
|
# test the transaction appears only once
|
||||||
|
last_voted_block = b.get_last_voted_block()
|
||||||
|
assert len(last_voted_block.transactions) == 1
|
||||||
|
assert b.backend.count_blocks() == 2
|
@ -121,7 +121,6 @@ def test_autoconfigure_read_both_from_file_and_env(monkeypatch):
|
|||||||
monkeypatch.setattr('bigchaindb.config_utils.file_config', lambda *args, **kwargs: file_config)
|
monkeypatch.setattr('bigchaindb.config_utils.file_config', lambda *args, **kwargs: file_config)
|
||||||
monkeypatch.setattr('os.environ', {'BIGCHAINDB_DATABASE_NAME': 'test-dbname',
|
monkeypatch.setattr('os.environ', {'BIGCHAINDB_DATABASE_NAME': 'test-dbname',
|
||||||
'BIGCHAINDB_DATABASE_PORT': '4242',
|
'BIGCHAINDB_DATABASE_PORT': '4242',
|
||||||
'BIGCHAINDB_API_ENDPOINT': 'api://ipa',
|
|
||||||
'BIGCHAINDB_SERVER_BIND': '1.2.3.4:56',
|
'BIGCHAINDB_SERVER_BIND': '1.2.3.4:56',
|
||||||
'BIGCHAINDB_KEYRING': 'pubkey_0:pubkey_1:pubkey_2'})
|
'BIGCHAINDB_KEYRING': 'pubkey_0:pubkey_1:pubkey_2'})
|
||||||
|
|
||||||
@ -151,7 +150,6 @@ def test_autoconfigure_read_both_from_file_and_env(monkeypatch):
|
|||||||
'port': 8125,
|
'port': 8125,
|
||||||
'rate': 0.01,
|
'rate': 0.01,
|
||||||
},
|
},
|
||||||
'api_endpoint': 'api://ipa',
|
|
||||||
'backlog_reassign_delay': 5
|
'backlog_reassign_delay': 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
tests/test_docs.py
Normal file
16
tests/test_docs.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_server_docs():
|
||||||
|
proc = subprocess.Popen(['bash'], stdin=subprocess.PIPE)
|
||||||
|
proc.stdin.write('cd docs/server; make html'.encode())
|
||||||
|
proc.stdin.close()
|
||||||
|
assert proc.wait() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_root_docs():
|
||||||
|
proc = subprocess.Popen(['bash'], stdin=subprocess.PIPE)
|
||||||
|
proc.stdin.write('cd docs/root; make html'.encode())
|
||||||
|
proc.stdin.close()
|
||||||
|
assert proc.wait() == 0
|
@ -1,4 +1,4 @@
|
|||||||
def test_api_endpoint_shows_basic_info(client):
|
def test_api_root_url_shows_basic_info(client):
|
||||||
from bigchaindb import version
|
from bigchaindb import version
|
||||||
res = client.get('/')
|
res = client.get('/')
|
||||||
assert res.json['software'] == 'BigchainDB'
|
assert res.json['software'] == 'BigchainDB'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import builtins
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -33,11 +34,12 @@ def test_post_create_transaction_endpoint(b, client):
|
|||||||
tx = tx.sign([user_priv])
|
tx = tx.sign([user_priv])
|
||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
|
||||||
assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == user_pub
|
assert res.json['fulfillments'][0]['owners_before'][0] == user_pub
|
||||||
assert res.json['transaction']['conditions'][0]['owners_after'][0] == user_pub
|
assert res.json['conditions'][0]['owners_after'][0] == user_pub
|
||||||
|
|
||||||
|
|
||||||
def test_post_create_transaction_with_invalid_id(b, client):
|
def test_post_create_transaction_with_invalid_id(b, client):
|
||||||
|
from bigchaindb.common.exceptions import InvalidHash
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
user_priv, user_pub = crypto.generate_key_pair()
|
user_priv, user_pub = crypto.generate_key_pair()
|
||||||
|
|
||||||
@ -47,18 +49,26 @@ def test_post_create_transaction_with_invalid_id(b, client):
|
|||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
assert res.status_code == 400
|
assert res.status_code == 400
|
||||||
|
err_msg = ("The transaction's id '{}' isn't equal to "
|
||||||
|
"the hash of its body, i.e. it's not valid.").format(tx['id'])
|
||||||
|
assert res.json['message'] == (
|
||||||
|
'Invalid transaction ({}): {}'.format(InvalidHash.__name__, err_msg))
|
||||||
|
|
||||||
|
|
||||||
def test_post_create_transaction_with_invalid_signature(b, client):
|
def test_post_create_transaction_with_invalid_signature(b, client):
|
||||||
|
from bigchaindb.common.exceptions import InvalidSignature
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
user_priv, user_pub = crypto.generate_key_pair()
|
user_priv, user_pub = crypto.generate_key_pair()
|
||||||
|
|
||||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
||||||
tx = tx.sign([user_priv]).to_dict()
|
tx = tx.sign([user_priv]).to_dict()
|
||||||
tx['transaction']['fulfillments'][0]['fulfillment'] = 'cf:0:0'
|
tx['fulfillments'][0]['fulfillment'] = 'cf:0:0'
|
||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
assert res.status_code == 400
|
assert res.status_code == 400
|
||||||
|
assert res.json['message'] == (
|
||||||
|
"Invalid transaction ({}): Fulfillment URI "
|
||||||
|
"couldn't been parsed".format(InvalidSignature.__name__))
|
||||||
|
|
||||||
|
|
||||||
def test_post_create_transaction_with_invalid_structure(client):
|
def test_post_create_transaction_with_invalid_structure(client):
|
||||||
@ -66,6 +76,49 @@ def test_post_create_transaction_with_invalid_structure(client):
|
|||||||
assert res.status_code == 400
|
assert res.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_create_transaction_with_invalid_schema(client):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
user_priv, user_pub = crypto.generate_key_pair()
|
||||||
|
tx = Transaction.create(
|
||||||
|
[user_pub], [([user_pub], 1)]).sign([user_priv]).to_dict()
|
||||||
|
del tx['version']
|
||||||
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
|
assert res.status_code == 400
|
||||||
|
assert res.json['message'] == (
|
||||||
|
"Invalid transaction schema: 'version' is a required property")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exc,msg', (
|
||||||
|
('AmountError', 'Do the math again!'),
|
||||||
|
('DoubleSpend', 'Nope! It is gone now!'),
|
||||||
|
('InvalidHash', 'Do not smoke that!'),
|
||||||
|
('InvalidSignature', 'Falsche Unterschrift!'),
|
||||||
|
('OperationError', 'Create and transfer!'),
|
||||||
|
('TransactionDoesNotExist', 'Hallucinations?'),
|
||||||
|
('TransactionOwnerError', 'Not yours!'),
|
||||||
|
('TransactionNotInValidBlock', 'Wait, maybe?'),
|
||||||
|
('ValueError', '?'),
|
||||||
|
))
|
||||||
|
def test_post_invalid_transaction(client, exc, msg, monkeypatch):
|
||||||
|
from bigchaindb.common import exceptions
|
||||||
|
try:
|
||||||
|
exc_cls = getattr(exceptions, exc)
|
||||||
|
except AttributeError:
|
||||||
|
exc_cls = getattr(builtins, 'ValueError')
|
||||||
|
|
||||||
|
def mock_validation(self_, tx):
|
||||||
|
raise exc_cls(msg)
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'bigchaindb.Bigchain.validate_transaction', mock_validation)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'bigchaindb.models.Transaction.from_dict', lambda tx: None)
|
||||||
|
res = client.post(TX_ENDPOINT, data=json.dumps({}))
|
||||||
|
assert res.status_code == 400
|
||||||
|
assert (res.json['message'] ==
|
||||||
|
'Invalid transaction ({}): {}'.format(exc, msg))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
|
def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
|
||||||
sk, pk = crypto.generate_key_pair()
|
sk, pk = crypto.generate_key_pair()
|
||||||
@ -81,8 +134,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
|
|||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||||
|
|
||||||
assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == user_pk
|
assert res.json['fulfillments'][0]['owners_before'][0] == user_pk
|
||||||
assert res.json['transaction']['conditions'][0]['owners_after'][0] == user_pub
|
assert res.json['conditions'][0]['owners_after'][0] == user_pub
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
|
42
tox.ini
Normal file
42
tox.ini
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[tox]
|
||||||
|
skipsdist = true
|
||||||
|
envlist = py34, py35, flake8, docsroot, docsserver
|
||||||
|
|
||||||
|
[base]
|
||||||
|
basepython = python3.5
|
||||||
|
deps = pip>=9.0.1
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
usedevelop = True
|
||||||
|
setenv =
|
||||||
|
PYTHONPATH = {toxinidir}:{toxinidir}/bigchaindb
|
||||||
|
deps = {[base]deps}
|
||||||
|
extras = test
|
||||||
|
commands = pytest -v -n auto --cov=bigchaindb --basetemp={envtmpdir}
|
||||||
|
|
||||||
|
[testenv:flake8]
|
||||||
|
basepython = {[base]basepython}
|
||||||
|
deps =
|
||||||
|
{[base]deps}
|
||||||
|
flake8
|
||||||
|
skip_install = True
|
||||||
|
extras = None
|
||||||
|
commands = flake8 --max-line-length 119 bigchaindb
|
||||||
|
|
||||||
|
[testenv:docsroot]
|
||||||
|
basepython = {[base]basepython}
|
||||||
|
changedir = docs/root/source
|
||||||
|
deps =
|
||||||
|
{[base]deps}
|
||||||
|
-r{toxinidir}/docs/root/requirements.txt
|
||||||
|
extras = None
|
||||||
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
||||||
|
|
||||||
|
[testenv:docsserver]
|
||||||
|
basepython = {[base]basepython}
|
||||||
|
changedir = docs/server/source
|
||||||
|
deps =
|
||||||
|
{[base]deps}
|
||||||
|
-r{toxinidir}/docs/server/requirements.txt
|
||||||
|
extras = None
|
||||||
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
Loading…
x
Reference in New Issue
Block a user