OpenCBDC Transaction Processor
Loading...
Searching...
No Matches
agent/runners/lua/impl.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 MIT Digital Currency Initiative,
2// Federal Reserve Bank of Boston
3// Distributed under the MIT software license, see the accompanying
4// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6#include "impl.hpp"
7
8#include "crypto/sha256.h"
11
12#include <cassert>
13#include <secp256k1.h>
14#include <secp256k1_schnorrsig.h>
15
17 static const auto secp_context
18 = std::unique_ptr<secp256k1_context,
19 decltype(&secp256k1_context_destroy)>(
20 secp256k1_context_create(SECP256K1_CONTEXT_VERIFY),
21 &secp256k1_context_destroy);
22
23 lua_runner::lua_runner(std::shared_ptr<logging::log> logger,
24 const cbdc::parsec::config& cfg,
26 parameter_type param,
27 bool is_readonly_run,
28 run_callback_type result_callback,
29 try_lock_callback_type try_lock_callback,
30 std::shared_ptr<secp256k1_context> secp,
31 std::shared_ptr<thread_pool> t_pool,
32 ticket_number_type ticket_number)
33 : interface(std::move(logger),
34 cfg,
35 std::move(function),
36 std::move(param),
37 is_readonly_run,
38 std::move(result_callback),
39 std::move(try_lock_callback),
40 std::move(secp),
41 std::move(t_pool),
42 ticket_number) {}
43
44 auto lua_runner::run() -> bool {
45 // TODO: use custom allocator to limit memory allocation
46 m_state
47 = std::shared_ptr<lua_State>(luaL_newstate(), [](lua_State* s) {
48 lua_close(s);
49 });
50
51 if(!m_state) {
52 m_log->error("Failed to allocate new lua state");
53 m_result_callback(error_code::internal_error);
54 return true;
55 }
56
57 // TODO: provide custom environment limited only to safe library
58 // methods
59 luaL_openlibs(m_state.get());
60
61 lua_register(m_state.get(), "check_sig", &lua_runner::check_sig);
62
63 static constexpr auto function_name = "contract";
64
65 auto load_ret = luaL_loadbufferx(m_state.get(),
66 m_function.c_str(),
67 m_function.size(),
68 function_name,
69 "b");
70 if(load_ret != LUA_OK) {
71 m_log->error("Failed to load function chunk");
72 m_result_callback(error_code::function_load);
73 return true;
74 }
75
76 if(lua_pushlstring(m_state.get(), m_param.c_str(), m_param.size())
77 == nullptr) {
78 m_log->error("Failed to push function params");
79 m_result_callback(error_code::internal_error);
80 return true;
81 }
82
83 schedule_contract();
84
85 return true;
86 }
87
88 void lua_runner::contract_epilogue(int n_results) {
89 if(n_results != 1) {
90 m_log->error("Contract returned more than one result");
91 m_result_callback(error_code::result_count);
92 return;
93 }
94
95 if(lua_istable(m_state.get(), -1) != 1) {
96 m_log->error("Contract did not return a table");
97 m_result_callback(error_code::result_type);
98 return;
99 }
100
102
103 lua_pushnil(m_state.get());
104 while(lua_next(m_state.get(), -2) != 0) {
105 auto key_buf = get_stack_string(-2);
106 if(!key_buf.has_value()) {
107 m_log->error("Result key is not a string");
108 m_result_callback(error_code::result_key_type);
109 return;
110 }
111 auto value_buf = get_stack_string(-1);
112 if(!value_buf.has_value()) {
113 m_log->error("Result value is not a string");
114 m_result_callback(error_code::result_value_type);
115 return;
116 }
117
118 results.emplace(std::move(key_buf.value()),
119 std::move(value_buf.value()));
120
121 lua_pop(m_state.get(), 1);
122 }
123
124 m_log->trace(this, "running calling result callback");
125 m_result_callback(std::move(results));
126 m_log->trace(this, "lua_runner finished contract epilogue");
127 }
128
129 auto lua_runner::get_stack_string(int index) -> std::optional<buffer> {
130 if(lua_isstring(m_state.get(), index) != 1) {
131 return std::nullopt;
132 }
133 size_t sz{};
134 const auto* str = lua_tolstring(m_state.get(), index, &sz);
135 assert(str != nullptr);
136 auto buf = buffer();
137 buf.append(str, sz);
138 return buf;
139 }
140
141 auto lua_runner::get_stack_integer(int index) -> std::optional<int64_t> {
142 if(lua_isinteger(m_state.get(), index) != 1) {
143 return std::nullopt;
144 }
145 return lua_tointeger(m_state.get(), index);
146 }
147
148 void lua_runner::schedule_contract() {
149 int n_results{};
150 auto resume_ret = lua_resume(m_state.get(), nullptr, 1, &n_results);
151 if(resume_ret == LUA_YIELD) {
152 if(n_results > 2) {
153 m_log->error("Contract yielded more than two keys");
154 m_result_callback(error_code::yield_count);
155 return;
156 }
157 if(n_results < 1) {
158 m_log->error("Contract yielded no keys");
159 m_result_callback(error_code::yield_count);
160 return;
161 }
162
163 auto lock_level = broker::lock_type::write;
164 if(n_results == 2) {
165 auto lock_type = get_stack_integer(-1);
166 if(!lock_type.has_value()) {
167 m_log->error("Contract yielded two keys, but the second "
168 "is not an integer");
169 m_result_callback(error_code::yield_type);
170 return;
171 }
172 lua_pop(m_state.get(), 1);
173
174 lock_level = (lock_type.value() == 0)
175 ? broker::lock_type::read
176 : broker::lock_type::write;
177 }
178
179 auto key_buf = get_stack_string(-1);
180 if(!key_buf.has_value()) {
181 m_log->error("Contract did not yield a string");
182 m_result_callback(error_code::yield_type);
183 return;
184 }
185
186 lua_pop(m_state.get(), 1);
187
188 auto success
189 = m_try_lock_callback(std::move(key_buf.value()),
190 lock_level,
191 [&](auto res) {
192 handle_try_lock(std::move(res));
193 });
194 if(!success) {
195 m_log->error("Failed to issue try lock command");
196 m_result_callback(error_code::internal_error);
197 }
198 } else if(resume_ret != LUA_OK) {
199 const auto* err = lua_tostring(m_state.get(), -1);
200 m_log->error("Error running contract:", err);
201 m_result_callback(error_code::exec_error);
202 } else {
203 contract_epilogue(n_results);
204 }
205 }
206
207 void lua_runner::handle_try_lock(
209 auto maybe_error = std::visit(
211 [&](const broker::value_type& v) -> std::optional<error_code> {
212 if(lua_pushlstring(m_state.get(), v.c_str(), v.size())
213 == nullptr) {
214 m_log->error("Failed to push yield params");
216 }
217 return std::nullopt;
218 },
219 [&](const broker::interface::error_code& /* e */)
220 -> std::optional<error_code> {
221 m_log->error("Broker error acquiring lock");
223 },
224 [&](const runtime_locking_shard::shard_error& e)
225 -> std::optional<error_code> {
226 if(e.m_error_code
228 return error_code::wounded;
229 }
230 m_log->error("Shard error acquiring lock");
232 }},
233 res);
234 if(maybe_error.has_value()) {
235 m_result_callback(maybe_error.value());
236 return;
237 }
238 schedule_contract();
239 }
240
241 auto lua_runner::check_sig(lua_State* L) -> int {
242 int n = lua_gettop(L);
243 if(n != 3) {
244 lua_pushliteral(L, "not enough arguments");
245 lua_error(L);
246 }
247 for(int i = 1; i <= n; i++) {
248 if(lua_isstring(L, i) != 1) {
249 lua_pushliteral(L, "invalid argument");
250 lua_error(L);
251 }
252 }
253
254 size_t sz{};
255 const auto* str = lua_tolstring(L, 1, &sz);
256 assert(str != nullptr);
257 pubkey_t key{};
258 if(sz != key.size()) {
259 lua_pushliteral(L, "invalid pubkey");
260 lua_error(L);
261 }
262 std::memcpy(key.data(), str, sz);
263
264 str = lua_tolstring(L, 2, &sz);
265 assert(str != nullptr);
266 signature_t sig{};
267 if(sz != sig.size()) {
268 lua_pushliteral(L, "invalid signature");
269 lua_error(L);
270 }
271 std::memcpy(sig.data(), str, sz);
272
273 secp256k1_xonly_pubkey pubkey{};
274 if(secp256k1_xonly_pubkey_parse(secp_context.get(),
275 &pubkey,
276 key.data())
277 != 1) {
278 lua_pushliteral(L, "invalid pubkey");
279 lua_error(L);
280 }
281
282 str = lua_tolstring(L, 3, &sz);
283 assert(str != nullptr);
284 auto sha = CSHA256();
285 auto unsigned_str = std::vector<unsigned char>(sz);
286 std::memcpy(unsigned_str.data(), str, sz);
287 sha.Write(unsigned_str.data(), sz);
288 hash_t sighash{};
289 sha.Finalize(sighash.data());
290
291 if(secp256k1_schnorrsig_verify(secp_context.get(),
292 sig.data(),
293 sighash.data(),
294 &pubkey)
295 != 1) {
296 lua_pushliteral(L, "invalid signature");
297 lua_error(L);
298 }
299
300 return 0;
301 }
302}
Buffer to store and retrieve byte data.
Definition buffer.hpp:15
std::function< void(run_return_type)> run_callback_type
Callback type for function execution.
std::function< bool(broker::key_type, broker::lock_type, broker::interface::try_lock_callback_type)> try_lock_callback_type
Callback function type for acquiring locks during function execution.
parsec::ticket_machine::ticket_number_type ticket_number_type
Type alias for a ticket number.
@ yield_count
Function yielded more than one key to lock.
@ wounded
Ticket wounded during execution.
@ result_count
Function more than one result.
@ result_value_type
Function did not return a string value.
@ yield_type
Function yielded a invalid datatype.
@ result_type
Function did not return a map.
@ result_key_type
Function did not return a string key.
@ exec_error
Runner error during function execution.
auto run() -> bool override
Begins function execution.
std::variant< value_type, error_code, runtime_locking_shard::shard_error > try_lock_return_type
Return type from a try lock operation.
error_code
Error codes returned by broker operations.
struct secp256k1_context_struct secp256k1_context
Definition keys.hpp:14
runtime_locking_shard::value_type value_type
Shard value type.
@ wounded
Request invalid because ticket is in the wounded state.
std:: unordered_map< key_type, value_type, hashing::const_sip_hash< key_type > > state_update_type
Type for state updates to a shard. A map of keys and their new values.
overloaded(Ts...) -> overloaded< Ts... >
std::array< unsigned char, cbdc::hash_size > hash_t
SHA256 hash container.
@ buffer
A singular RLP value (byte array)
std::array< unsigned char, sig_len > signature_t
A signature.
Definition keys.hpp:29
std::array< unsigned char, pubkey_len > pubkey_t
A public key of a public/private keypair.
Definition keys.hpp:25
Configuration parameters for a phase two system.