Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add read locks to the Lua runner #260

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions scripts/gen_bytecode.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
function gen_bytecode()
pay_contract = function(param)
-- 0 is a read lock, 1 is a write lock in C++ scope
Locks = {
READ = 0,
WRITE = 1,
}
from, to, value, sequence, sig = string.unpack("c32 c32 I8 I8 c64", param)

function get_account_key(name)
Expand All @@ -8,9 +13,12 @@ function gen_bytecode()

function get_account(name)
account_key = get_account_key(name)
account_data = coroutine.yield(account_key)
if #account_data > 0 then
return string.unpack("I8 I8", account_data)

account_data = coroutine.yield(account_key, Locks.WRITE)
if string.len(account_data) > 0 then
account_balance, account_sequence
= string.unpack("I8 I8", account_data)
return account_balance, account_sequence
end
return 0, 0
end
Expand Down
2 changes: 1 addition & 1 deletion src/parsec/agent/runners/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace cbdc::parsec::agent::runner {
internal_error,
/// Function yielded more than one key to lock.
yield_count,
/// Function yielded a non-string key.
/// Function yielded a invalid datatype.
yield_type,
/// Error acquiring lock on key.
lock_error,
Expand Down
39 changes: 35 additions & 4 deletions src/parsec/agent/runners/lua/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,56 @@ namespace cbdc::parsec::agent::runner {
return buf;
}

auto lua_runner::get_stack_integer(int index) -> std::optional<int64_t> {
if(lua_isinteger(m_state.get(), index) != 1) {
maurermi marked this conversation as resolved.
Show resolved Hide resolved
return std::nullopt;
}
return lua_tointeger(m_state.get(), index);
}

void lua_runner::schedule_contract() {
int n_results{};
auto resume_ret = lua_resume(m_state.get(), nullptr, 1, &n_results);
if(resume_ret == LUA_YIELD) {
if(n_results != 1) {
m_log->error("Contract yielded more than one key");
if(n_results > 2) {
m_log->error("Contract yielded more than two keys");
m_result_callback(error_code::yield_count);
return;
}
if(n_results < 1) {
m_log->error("Contract yielded no keys");
m_result_callback(error_code::yield_count);
return;
}

auto lock_level = broker::lock_type::write;
if(n_results == 2) {
maurermi marked this conversation as resolved.
Show resolved Hide resolved
auto lock_type = get_stack_integer(-1);
if(!lock_type.has_value()) {
m_log->error("Contract yielded two keys, but the second "
"is not an integer");
m_result_callback(error_code::yield_type);
return;
}
lua_pop(m_state.get(), 1);

lock_level = (lock_type.value() == 0)
? broker::lock_type::read
: broker::lock_type::write;
}

auto key_buf = get_stack_string(-1);
if(!key_buf.has_value()) {
m_log->error("Contract did not yield a string");
m_result_callback(error_code::yield_type);
return;
}
lua_pop(m_state.get(), n_results);

lua_pop(m_state.get(), 1);

auto success
= m_try_lock_callback(std::move(key_buf.value()),
broker::lock_type::write,
lock_level,
[&](auto res) {
handle_try_lock(std::move(res));
});
Expand Down
6 changes: 6 additions & 0 deletions src/parsec/agent/runners/lua/impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ namespace cbdc::parsec::agent::runner {
/// function execution, signature checking and commiting execution results.
/// Class cannot be re-used for different functions/transactions, manages
/// the lifecycle of a single transaction.
/// NOTE: When writing contracts, to pass data between the Lua environment
/// and the C++ environment, use `coroutine.yield()`. To request a
/// read-lock use coroutine.yield(<data>, 0). To request a write-lock use
/// coroutine.yield(<data>, 1) or coroutine.yield(<data>).
class lua_runner : public interface {
public:
/// \copydoc interface::interface()
Expand Down Expand Up @@ -47,6 +51,8 @@ namespace cbdc::parsec::agent::runner {

auto get_stack_string(int index) -> std::optional<buffer>;

auto get_stack_integer(int index) -> std::optional<int64_t>;

void schedule_contract();

void
Expand Down
136 changes: 136 additions & 0 deletions tests/unit/parsec/agent/runners/lua/read_lock_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2021 MIT Digital Currency Initiative,
// Federal Reserve Bank of Boston
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "../../../util.hpp"
#include "crypto/sha256.h"
#include "parsec/agent/impl.hpp"
#include "parsec/agent/runners/lua/impl.hpp"
#include "parsec/broker/impl.hpp"
#include "parsec/directory/impl.hpp"
#include "parsec/runtime_locking_shard/impl.hpp"
#include "parsec/ticket_machine/impl.hpp"
#include "parsec/util.hpp"
#include "util/common/keys.hpp"
#include "util/serialization/buffer_serializer.hpp"
#include "util/serialization/format.hpp"

#include <future>
#include <gtest/gtest.h>
#include <lua.hpp>
#include <secp256k1.h>
#include <secp256k1_schnorrsig.h>
#include <thread>

TEST(lua_runner_test, lua_write_lock_test) {
auto log = std::make_shared<cbdc::logging::log>(
cbdc::logging::log_level::trace);
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L,
"../tests/unit/parsec/agent/runners/lua/test_write_locks.lua");
lua_getglobal(L, "gen_bytecode");
ASSERT_EQ(lua_pcall(L, 0, 1, 0), 0);
auto contract = cbdc::buffer::from_hex(lua_tostring(L, -1)).value();
auto func = cbdc::buffer::from_hex(contract.to_hex()).value();
auto param = cbdc::buffer();
auto cfg = cbdc::parsec::config();

auto result_cb = [&](cbdc::parsec::agent::runner::interface::
run_return_type /* value */) {
return;
};

std::promise<void> write_lock_promise;
auto write_lock_future = write_lock_promise.get_future();
auto try_lock_cb
= [&](const cbdc::parsec::broker::key_type& key,
cbdc::parsec::broker::lock_type locktype,
const cbdc::parsec::broker::interface::try_lock_callback_type&
/* res_cb */) -> bool {
// Cannot use ASSERT here because it does not satisfy return
// requirements so we use expect
if(std::string("W").compare(key.c_str()) == 0) {
EXPECT_TRUE(locktype == cbdc::parsec::broker::lock_type::write);
write_lock_promise.set_value();
} else {
// Cannot use FAIL() here because it expands to a return statement
// which does not satisfy the requirements of a try_lock_callback
// so we improvise
EXPECT_FALSE(true);
}
return true;
};

auto runner
= cbdc::parsec::agent::runner::lua_runner(log,
cfg,
std::move(func),
std::move(param),
false,
std::move(result_cb),
std::move(try_lock_cb),
nullptr,
nullptr,
0);
auto res = runner.run();
write_lock_future.get();
ASSERT_TRUE(res);
}

TEST(lua_runner_test, lua_read_lock_test) {
auto log = std::make_shared<cbdc::logging::log>(
cbdc::logging::log_level::trace);
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L,
"../tests/unit/parsec/agent/runners/lua/test_read_locks.lua");
lua_getglobal(L, "gen_bytecode");
ASSERT_EQ(lua_pcall(L, 0, 1, 0), 0);
auto contract = cbdc::buffer::from_hex(lua_tostring(L, -1)).value();
auto func = cbdc::buffer::from_hex(contract.to_hex()).value();
auto param = cbdc::buffer();
auto cfg = cbdc::parsec::config();

auto result_cb = [&](cbdc::parsec::agent::runner::interface::
run_return_type /* value */) {
return;
};

std::promise<void> read_lock_promise;
auto read_lock_future = read_lock_promise.get_future();
auto try_lock_cb
= [&](const cbdc::parsec::broker::key_type& key,
cbdc::parsec::broker::lock_type locktype,
const cbdc::parsec::broker::interface::try_lock_callback_type&
/* res_cb */) -> bool {
// Cannot use ASSERT here because it does not satisfy return
// requirements so we use expect
if(std::string("R").compare(key.c_str()) == 0) {
EXPECT_TRUE(locktype == cbdc::parsec::broker::lock_type::read);
read_lock_promise.set_value();
} else {
// Cannot use FAIL() here because it expands to a return statement
// which does not satisfy the requirements of a try_lock_callback
// so we improvise
EXPECT_FALSE(true);
}
return true;
};

auto runner
= cbdc::parsec::agent::runner::lua_runner(log,
cfg,
std::move(func),
std::move(param),
false,
std::move(result_cb),
std::move(try_lock_cb),
nullptr,
nullptr,
0);
auto res = runner.run();
read_lock_future.get();
ASSERT_TRUE(res);
}
17 changes: 17 additions & 0 deletions tests/unit/parsec/agent/runners/lua/test_read_locks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function gen_bytecode()
pay_contract = function(param)
Locks = {
READ = 0,
WRITE = 1,
}
data = coroutine.yield("R", Locks.READ)
return "Done"
end
c = string.dump(pay_contract, true)
t = {}
for i = 1, #c do
t[#t + 1] = string.format("%02x", string.byte(c, i))
end

return table.concat(t)
end
17 changes: 17 additions & 0 deletions tests/unit/parsec/agent/runners/lua/test_write_locks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function gen_bytecode()
pay_contract = function(param)
Locks = {
READ = 0,
WRITE = 1,
}
data2 = coroutine.yield("W", Locks.WRITE)
return "Done"
end
c = string.dump(pay_contract, true)
t = {}
for i = 1, #c do
t[#t + 1] = string.format("%02x", string.byte(c, i))
end

return table.concat(t)
end
93 changes: 93 additions & 0 deletions tests/unit/parsec/agent/runners/lua/write_lock_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2021 MIT Digital Currency Initiative,
// Federal Reserve Bank of Boston
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "../../../util.hpp"
#include "crypto/sha256.h"
#include "parsec/agent/impl.hpp"
#include "parsec/agent/runners/lua/impl.hpp"
#include "parsec/broker/impl.hpp"
#include "parsec/directory/impl.hpp"
#include "parsec/runtime_locking_shard/impl.hpp"
#include "parsec/ticket_machine/impl.hpp"
#include "parsec/util.hpp"
#include "util/common/keys.hpp"
#include "util/serialization/buffer_serializer.hpp"
#include "util/serialization/format.hpp"

#include <future>
#include <gtest/gtest.h>
#include <lua.hpp>
#include <secp256k1.h>
#include <secp256k1_schnorrsig.h>
#include <thread>

TEST(lua_runner_test, lua_read_lock_test) {
auto log = std::make_shared<cbdc::logging::log>(
cbdc::logging::log_level::trace);
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L,
"../tests/unit/parsec/agent/runners/lua/test_write_locks.lua");
lua_getglobal(L, "gen_bytecode");
ASSERT_EQ(lua_pcall(L, 0, 1, 0), 0);
auto contract = cbdc::buffer::from_hex(lua_tostring(L, -1)).value();
auto func = cbdc::buffer::from_hex(contract.to_hex()).value();
auto param = cbdc::buffer();
auto cfg = cbdc::parsec::config();

auto result_cb = [&](cbdc::parsec::agent::runner::interface::
run_return_type /* value */) {
return;
};

std::atomic<int> lock_count = 0;
std::promise<void> write_lock_promise;
std::promise<void> read_lock_promise;
auto write_lock_future = write_lock_promise.get_future();
auto read_lock_future = read_lock_promise.get_future();
auto try_lock_cb
= [&](const cbdc::parsec::broker::key_type& key,
cbdc::parsec::broker::lock_type locktype,
const cbdc::parsec::broker::interface::try_lock_callback_type&
/* res_cb */) -> bool {
// Cannot use ASSERT here because it does not satisfy return
// requirements so we use expect
log->trace("Got lock for key:", key.c_str());
if(std::string("W").compare(key.c_str()) == 0) {
log->trace("Got write lock");
EXPECT_TRUE(locktype == cbdc::parsec::broker::lock_type::write);
lock_count++;
write_lock_promise.set_value();
} else if(std::string("R").compare(key.c_str()) == 0) {
log->trace("Got read lock");
EXPECT_TRUE(locktype == cbdc::parsec::broker::lock_type::read);
lock_count++;
read_lock_promise.set_value();
} else {
// Cannot use FAIL() here because it expands to a return statement
// which does not satisfy the requirements of a try_lock_callback
// so we improvise
EXPECT_FALSE(true);
}
return true;
};

auto runner
= cbdc::parsec::agent::runner::lua_runner(log,
cfg,
std::move(func),
std::move(param),
false,
std::move(result_cb),
std::move(try_lock_cb),
nullptr,
nullptr,
0);
auto res = runner.run();
write_lock_future.get();
read_lock_future.get();
ASSERT_EQ(lock_count, 2);
ASSERT_TRUE(res);
}
Loading