Skip to content

Commit bb729e0

Browse files
Refactor user nonce logic. (#649)
* Refactor & removed get_or_create_nonce function. * Skipped c2d tests. Removed c2d from workflow. * Updated pytest.yml. * Fixed import. * tweak. * Skipped tests for compute. Fixed nonce functions. * Synchronized nonce operations in compute endpoints. * Removed locks. * Fixed bug in nonce endpoint. * Converted timestamp to simple integer for nonce. Updated build_nonce for testing and nonce endpoint. * Added couple fixes. Removed timestamp computation for nonce. * Added test for nonce. * Fixed requests tweak. * Replace text with content. * Refactored _compute_nonce function. * Changed parameter. * Deleted _compute_nonce. * Update API.md. * Bump version: 2.0.2 → 2.1.0 --------- Co-authored-by: alexcos20 <alex.coseru@gmail.com>
1 parent 176564b commit bb729e0

22 files changed

+400
-89
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 2.0.3
2+
current_version = 2.1.0
33
commit = True
44
tag = True
55

API.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ Parameters
2020

2121
Returns:
2222
Json object containing the last-used nonce value.
23-
The nonce endpoint is just informative, use the current UTC timestamp as a nonce,
24-
where required in other endpoints.
23+
The nonce returns an int, where required in other Provider endpoints.
2524

2625
Example:
2726

@@ -34,7 +33,7 @@ Response:
3433

3534
```json
3635
{
37-
"nonce": 1644315615.24195
36+
"nonce": 1
3837
}
3938
```
4039

@@ -76,7 +75,7 @@ Parameters
7675
encryptedDocument: Hex string, the encrypted document (optional)
7776
flags: Integer, the flags of the encrypted document (optional)
7877
documentHash: Hex string, the hash of the encrypted document (optional)
79-
nonce: String object, the nonce of the encrypted document (required)
78+
nonce: String object, the nonce (either integer, either UTC timestamp format) of the encrypted document (required)
8079
signature: the signature of the encrypted document (required).
8180
The signature is based on hashing the string concatenation consisting of:
8281
transactionId + dataNftAddress + decrypterAddress + chainId + nonce.
@@ -181,7 +180,7 @@ Parameters
181180
transferTxId: Hex string -- the id of on-chain transaction for approval of datatokens transfer
182181
given to the provider's account
183182
fileIndex: integer, the index of the file from the files list in the dataset
184-
nonce: Nonce
183+
nonce: String object, the nonce (either integer, either UTC timestamp format) required for download request
185184
consumerAddress: String object containing consumer's address
186185
signature: String object containg user signature (signed message).
187186
The signature is based on hashing the following string concatenation consisting of:
@@ -205,6 +204,8 @@ payload:
205204
"consumerAddress":"0x990922334",
206205
"signature":"0x00110011",
207206
"transferTxId": "0xa09fc23421345532e34829"
207+
"nonce": 1
208+
}
208209
```
209210

210211
Response:

ocean_provider/routes/consume.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#
55
import json
66
import logging
7+
from datetime import datetime, timezone
78

89
from flask import jsonify, request
910
from flask_sieve import validate
@@ -62,9 +63,16 @@ def nonce():
6263
data = get_request_data(request)
6364
address = data.get("userAddress")
6465
nonce = get_nonce(address)
66+
67+
if not nonce:
68+
new_nonce = 1
69+
update_nonce(address, new_nonce)
70+
nonce = get_nonce(address)
71+
assert int(nonce) == new_nonce, "New nonce could not be stored correctly."
72+
6573
logger.info(f"nonce for user {address} is {nonce}")
6674

67-
response = jsonify(nonce=nonce), 200
75+
response = jsonify(nonce=int(nonce)), 200
6876
logger.info(f"nonce response = {response}")
6977

7078
return response

ocean_provider/test/test_user_nonce.py

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33
# SPDX-License-Identifier: Apache-2.0
44
#
55
import os
6-
from unittest.mock import patch
6+
import sqlite3
77

88
import pytest
9-
import sqlalchemy
109
from flask_caching import Cache
11-
from ocean_provider import models, user_nonce
1210
from ocean_provider.myapp import app
1311
from ocean_provider.user_nonce import (
1412
get_nonce,
15-
get_or_create_user_nonce_object,
1613
update_nonce,
1714
)
1815
from tests.helpers.nonce import build_nonce
@@ -34,15 +31,10 @@ def test_get_and_update_nonce(monkeypatch, publisher_address, consumer_address):
3431

3532
# get_nonce can be used on addresses that are not in the user_nonce table
3633
assert get_nonce("0x0000000000000000000000000000000000000000") is None
37-
assert get_or_create_user_nonce_object(
38-
"0x0000000000000000000000000000000000000000", build_nonce()
39-
)
4034

4135
# update two times because, if we just pruned, we start from None
42-
update_nonce(publisher_address, build_nonce())
43-
publisher_nonce = get_nonce(publisher_address)
44-
update_nonce(publisher_address, build_nonce())
45-
new_publisher_nonce = get_nonce(publisher_address)
36+
publisher_nonce = build_nonce(publisher_address)
37+
new_publisher_nonce = build_nonce(publisher_address)
4638

4739
assert new_publisher_nonce >= publisher_nonce
4840

@@ -56,14 +48,11 @@ def test_get_and_update_nonce_redis(publisher_address, consumer_address):
5648
# get_nonce can be used on addresses that are not in the user_nonce table
5749
cache.delete("0x0000000000000000000000000000000000000000")
5850
assert get_nonce("0x0000000000000000000000000000000000000000") is None
59-
assert get_or_create_user_nonce_object(
60-
"0x0000000000000000000000000000000000000000", build_nonce()
61-
)
6251

6352
# update two times because, if we just pruned, we start from None
64-
update_nonce(publisher_address, build_nonce())
53+
update_nonce(publisher_address, build_nonce(publisher_address))
6554
publisher_nonce = get_nonce(publisher_address)
66-
update_nonce(publisher_address, build_nonce())
55+
update_nonce(publisher_address, build_nonce(publisher_address))
6756
new_publisher_nonce = get_nonce(publisher_address)
6857

6958
assert new_publisher_nonce >= publisher_nonce
@@ -78,17 +67,11 @@ def test_update_nonce_exception(monkeypatch, publisher_address):
7867
# pass through sqlite
7968
monkeypatch.delenv("REDIS_CONNECTION")
8069

81-
# Ensure address exists in database
82-
update_nonce(publisher_address, build_nonce())
70+
nonce_object = get_nonce(publisher_address)
8371

8472
# Create duplicate nonce_object
85-
with patch.object(
86-
user_nonce,
87-
"get_or_create_user_nonce_object",
88-
return_value=models.UserNonce(address=publisher_address, nonce="0"),
89-
):
90-
with pytest.raises(sqlalchemy.exc.IntegrityError):
91-
update_nonce(publisher_address, build_nonce())
73+
with pytest.raises(sqlite3.IntegrityError):
74+
update_nonce(publisher_address, nonce_object)
9275

9376
publisher_nonce = get_nonce(publisher_address)
9477
update_nonce(publisher_address, None)

ocean_provider/user_nonce.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#
55
import logging
66
import os
7+
import sqlite3
78

89
import jwt
910
from flask_caching import Cache
@@ -45,18 +46,28 @@ def update_nonce(address, nonce_value):
4546
:param: nonce_value
4647
"""
4748
if nonce_value is None:
49+
logger.debug(f"Nonce value is not provided.")
4850
return
4951

52+
logger.debug(f"Received nonce value: {nonce_value}")
53+
5054
if os.getenv("REDIS_CONNECTION"):
51-
nonce = get_or_create_user_nonce_object(address, nonce_value)
52-
cache.set(address, nonce)
55+
cache.set(address, nonce_value)
5356

5457
return
5558

56-
nonce_object = get_or_create_user_nonce_object(address, nonce_value)
57-
nonce_object.nonce = nonce_value
59+
nonce_object = models.UserNonce.query.filter_by(address=address).first()
60+
if nonce_object is None:
61+
nonce_object = models.UserNonce(address=address, nonce=nonce_value)
62+
else:
63+
if nonce_object.nonce == nonce_value:
64+
msg = f"Cannot create duplicates in the database.\n Existing nonce: {nonce_object.nonce} vs. new nonce: {nonce_value}"
65+
logger.debug(msg)
66+
raise sqlite3.IntegrityError(msg)
67+
68+
nonce_object.nonce = nonce_value
5869

59-
logger.debug(f"update_nonce: {address}, new nonce {nonce_object.nonce}")
70+
logger.debug(f"Wallet address: {address}, new nonce {nonce_object.nonce}")
6071

6172
try:
6273
db.add(nonce_object)
@@ -67,18 +78,6 @@ def update_nonce(address, nonce_value):
6778
raise
6879

6980

70-
def get_or_create_user_nonce_object(address, nonce_value):
71-
if os.getenv("REDIS_CONNECTION"):
72-
cache.set(address, nonce_value)
73-
74-
return nonce_value
75-
76-
nonce_object = models.UserNonce.query.filter_by(address=address).first()
77-
if nonce_object is None:
78-
nonce_object = models.UserNonce(address=address, nonce=nonce_value)
79-
return nonce_object
80-
81-
8281
def force_expire_token(token):
8382
"""
8483
Creates the token in the database of Revoked Tokens.

ocean_provider/utils/compute.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from eth_keys import KeyAPI
1111
from eth_keys.backends import NativeECCBackend
12+
from ocean_provider.user_nonce import get_nonce
1213
from ocean_provider.utils.accounts import sign_message
1314
from ocean_provider.utils.basics import get_provider_wallet
1415

@@ -46,7 +47,7 @@ def process_compute_request(data):
4647

4748

4849
def sign_for_compute(wallet, owner, job_id=None):
49-
nonce = datetime.now(timezone.utc).timestamp()
50+
nonce = datetime.now(timezone.utc).timestamp() * 1000
5051

5152
# prepare consumer signature on did
5253
msg = f"{owner}{job_id}{nonce}" if job_id else f"{owner}{nonce}"

ocean_provider/utils/test/test_accounts.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,15 @@ def test_get_private_key(publisher_wallet):
2222

2323
@pytest.mark.unit
2424
def test_verify_signature(consumer_wallet, publisher_wallet):
25-
update_nonce(consumer_wallet.address, build_nonce())
26-
27-
nonce = build_nonce()
25+
nonce = build_nonce(consumer_wallet.address)
2826
did = "did:op:test"
2927
msg = f"{consumer_wallet.address}{did}{nonce}"
3028
msg_w_nonce = f"{consumer_wallet.address}{did}"
3129
signature = sign_message(msg, consumer_wallet)
3230

3331
assert verify_signature(consumer_wallet.address, signature, msg_w_nonce, nonce)
3432

35-
nonce = build_nonce()
33+
nonce = build_nonce(consumer_wallet.address)
3634
did = "did:op:test"
3735
msg = f"{consumer_wallet.address}{did}{nonce}"
3836
msg_w_nonce = f"{consumer_wallet.address}{did}"
@@ -43,7 +41,7 @@ def test_verify_signature(consumer_wallet, publisher_wallet):
4341

4442
assert f"Invalid signature {signature} for ethereum address" in e_info.value.args[0]
4543

46-
nonce = (datetime.now(timezone.utc) - timedelta(days=7)).timestamp()
44+
nonce = 1
4745
did = "did:op:test"
4846
msg = f"{consumer_wallet.address}{did}{nonce}"
4947
msg_w_nonce = f"{consumer_wallet.address}{did}"

ocean_provider/utils/test/test_compute.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
@pytest.mark.unit
17+
@pytest.mark.skip("C2D connection needs fixing.")
1718
def test_get_compute_endpoint(monkeypatch):
1819
monkeypatch.setenv("OPERATOR_SERVICE_URL", "http://with-slash.com/")
1920
assert get_compute_endpoint() == "http://with-slash.com/api/v1/operator/compute"

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
url="https://github.com/oceanprotocol/provider-py",
101101
# fmt: off
102102
# bumpversion needs single quotes
103-
version='2.0.3',
103+
version='2.1.0',
104104
# fmt: on
105105
zip_safe=False,
106106
)

tests/helpers/compute_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def build_and_send_ddo_with_compute_service(
155155

156156

157157
def get_compute_signature(client, consumer_wallet, did, job_id=None):
158-
nonce = build_nonce()
158+
nonce = build_nonce(consumer_wallet.address)
159159

160160
# prepare consumer signature on did
161161
if job_id:

0 commit comments

Comments
 (0)