Skip to content

Commit

Permalink
Merge pull request #62 from xian-network/randomx-stamp-costs
Browse files Browse the repository at this point in the history
Randomx stamp costs
  • Loading branch information
duelingbenjos authored Sep 19, 2024
2 parents ea0d831 + 5615e9b commit 1c38027
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 8 deletions.
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ def pkgconfig(package):
"pycodestyle==2.10.0",
"autopep8==1.5.7",
"iso8601",
"h5py",
"cachetools",
"loguru",
"pynacl"
"pynacl",
"randomx",
"h5py",
],
url="https://github.com/xian-network/contracting",
author="Xian",
Expand Down
2 changes: 1 addition & 1 deletion src/contracting/execution/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def execute(self, sender, contract_name, function_name, kwargs,
stamps_used = runtime.rt.tracer.get_stamp_used()

stamps_used = stamps_used // 1000
stamps_used += 1
stamps_used += 5

if stamps_used > stamps:
stamps_used = stamps
Expand Down
49 changes: 44 additions & 5 deletions src/contracting/execution/metering/tracer.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ unsigned long long cu_costs[256] = {

unsigned long long MAX_STAMPS = 6500000;

#define CRYPTO_MODULE_NAME "contracting.stdlib.bridge.crypto"
#define RANDOMX_FUNCTION_NAME "randomx_hash"

/* The Tracer type. */

typedef struct {
Expand Down Expand Up @@ -134,18 +137,50 @@ Tracer_trace(Tracer * self, PyFrameObject * frame, int what, PyObject * arg) {
return RET_ERROR; // Use an appropriate return code
}

// Check if the current function matches the target module and function names
PyCodeObject *code = PyFrame_GetCode(frame);
if (code == NULL) {
return RET_OK;
}
const char *current_function_name = PyUnicode_AsUTF8(code->co_name);
if (current_function_name == NULL) {
Py_DECREF(code);
return RET_OK;
}
PyObject *globals = PyFrame_GetGlobals(frame);
if (globals == NULL) {
Py_DECREF(code);
return RET_OK;
}
PyObject *module_name_obj = PyDict_GetItemString(globals, "__name__");
if (module_name_obj == NULL) {
Py_DECREF(globals);
Py_DECREF(code);
return RET_OK;
}
const char *current_module_name = PyUnicode_AsUTF8(module_name_obj);
if (current_module_name == NULL) {
Py_DECREF(globals);
Py_DECREF(code);
return RET_OK;
}
if (strcmp(current_function_name, RANDOMX_FUNCTION_NAME) == 0 &&
strcmp(current_module_name, CRYPTO_MODULE_NAME) == 0) {
self->cost += 100000; // Increment the cost by a specific value (e.g., 100)
}


unsigned long long estimate = 0;
unsigned long long factor = 1000;
const char * str;
// IF, Frame object globals contains __contract__ and it is true, continue
PyObject * kv = PyUnicode_FromString("__contract__");
PyObject * globals = PyFrame_GetGlobals(frame);
int t = PyDict_Contains(globals, kv);
Py_DECREF(globals);
Py_DECREF(kv);

if (t != 1) {
Py_DECREF(globals);
Py_DECREF(code);
return RET_OK;
}

Expand All @@ -157,9 +192,7 @@ Tracer_trace(Tracer * self, PyFrameObject * frame, int what, PyObject * arg) {

switch (what) {
case PyTrace_LINE: /* 2 */ {
PyObject * code = PyFrame_GetCode(frame); // Declare 'code' variable here
const char * str = PyBytes_AS_STRING(PyCode_GetCode((PyCodeObject * ) code));
Py_DECREF(code);
const char * str = PyBytes_AS_STRING(PyCode_GetCode(code));
int lasti = PyFrame_GetLasti(frame);
opcode = str[lasti];

Expand All @@ -180,6 +213,8 @@ Tracer_trace(Tracer * self, PyFrameObject * frame, int what, PyObject * arg) {
PyErr_SetString(PyExc_AssertionError, "The cost has exceeded the stamp supplied!");
PyEval_SetTrace(NULL, NULL);
self -> started = 0;
Py_DECREF(globals);
Py_DECREF(code);
return RET_ERROR;
}

Expand All @@ -192,6 +227,8 @@ Tracer_trace(Tracer * self, PyFrameObject * frame, int what, PyObject * arg) {
#endif
PyEval_SetTrace(NULL, NULL);
self -> started = 0;
Py_DECREF(globals);
Py_DECREF(code);
return RET_ERROR;
}
//printf("Opcode: %d\n Cost: %lld\n Total Cost: %lld\n", opcode, cu_costs[opcode], self -> cost);
Expand All @@ -202,6 +239,8 @@ Tracer_trace(Tracer * self, PyFrameObject * frame, int what, PyObject * arg) {
break;
}

Py_DECREF(globals);
Py_DECREF(code);
return RET_OK;
}

Expand Down
15 changes: 15 additions & 0 deletions src/contracting/stdlib/bridge/crypto.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from types import ModuleType
import randomx
import nacl


Expand Down Expand Up @@ -27,9 +28,23 @@ def key_is_valid(key: str):
return True


def randomx_hash(key: str, message: str):
try:
key_bytes = bytes.fromhex(key)
except ValueError:
key_bytes = key.encode()
try:
message_bytes = bytes.fromhex(message)
except ValueError:
message_bytes = message.encode()
vm = randomx.RandomX(key_bytes, full_mem=False, secure=False, large_pages=False)
return vm(message_bytes).hex()


crypto_module = ModuleType('crypto')
crypto_module.verify = verify
crypto_module.key_is_valid = key_is_valid
crypto_module.randomx_hash = randomx_hash

exports = {
'crypto': crypto_module
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/test_crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from unittest import TestCase
from contracting.stdlib.bridge.crypto import randomx_hash
import random
import string
import binascii

class TestCrypto(TestCase):

def random_string(self, length):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

def random_hex_string(self, length):
return ''.join(random.choices('0123456789abcdef', k=length))

def test_randomx_hash(self):

expected = [
"7ba1fefe2ac793fcdea6436b8fb0cbc3254e2a289027896d9a9d0a747759897f",
"d2d88402817eb5d37be0e4b80ee3670e596ce1303c22c87c2d576b63e4c3283f",
"4d9f9aa4aa4f27eac09f700a52047b76d1168936fa01e352aeeb172c1eb56005",
"fb8e209d45012ef72583bd846ba6c8dfa86e59f3a59c769f7ea3f55bbb9b9f9b",
"7798a6d5cb45fe0aa79dae48a50138e52b1bf0c0a9fd7664306ccffbde660612",
]

for x in range(5):
m = "Hello RandomX {}".format(x)
print("Hashing: {}".format(m))
res = randomx_hash('63eceef7919087068ac5d1b7faffa23fc90a58ad0ca89ecb224a2ef7ba282d48', m + str(x))
self.assertEqual(res, expected[x])
self.assertEqual(str(type(res)), "<class 'str'>")

def test_randomx_hash_invalid_hex_key(self):
key = 'invalidhexkey'
message = '4d657373616765'
res = randomx_hash(key, message)
self.assertEqual(res, 'f2649fde52dc7a395e9be604116ff46299130d4e57d745331f23c5538c125ef5')

def test_randomx_hash_empty_key(self):
key = ''
message = '4d657373616765'
res = randomx_hash(key, message)
self.assertEqual(res, "b5b573af85a6880b3e0c391d22e0700d8cd375948386da31d0c534bd72c6e30f")

def test_randomx_hash_fuzz_mixed_input(self):
for _ in range(100):
key = self.random_string(random.randint(1, 128))
message = self.random_string(random.randint(1, 128))
try:
result = randomx_hash(key, message)
self.assertIsInstance(result, str)
self.assertEqual(len(result), 64) # Assuming the hash length is 64 hex characters
except Exception as e:
self.fail(f"randomx_hash raised an exception with mixed input: {e}")

0 comments on commit 1c38027

Please sign in to comment.