More script work, especially with blob deduplication

This commit is contained in:
Crypto City 2020-11-17 19:14:30 +00:00
parent b2b89c6d00
commit 91d09e545e
11 changed files with 136 additions and 67 deletions

2
external/tb vendored

@ -1 +1 @@
Subproject commit 7ad8019d0e1e6ade950ea441b63996e1d42b6bd2
Subproject commit 24487e5a2f27c6df8e7ecdbfa45f623512f0e197

View File

@ -26,6 +26,7 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "string_tools.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_db/locked_txn.h"
@ -160,6 +161,10 @@ bool cc_command_handler_start_script::execute(cryptonote::BlockchainDB &db, cons
if (std::get<0>(e) == cc::script::override_string)
string_overrides[std::get<1>(e)] = std::get<2>(e);
}
for (auto &e: string_overrides)
{
cc::script::resolve_reference(db, e.second); // ignore errors, we might have 32 byte text and not a ref
}
db.set_cc_account_script_state(start_script.cc_account, start_script.script, owner, 0, start_script.city, string_overrides);
for (const auto &e: overrides)

View File

@ -294,6 +294,25 @@ bool load_script_binary(const std::string &blob, ScriptHandle &script)
return true;
}
template<typename S, typename F>
static bool foreach_blob_candidate(S *s, const F &f)
{
if (!f(s->icon)) return false;
for (auto &o: s->overrides)
{
if (o.type == override_string)
if (!f(o.text))
return false;
}
for (auto &state: s->states)
{
if (!f(state.image)) return false;
if (!f(state.ui)) return false;
if (!f(state.choices_ui)) return false;
}
return true;
}
crypto::hash get_blob_hash(const cryptonote::blobdata &blob)
{
crypto::hash hash;
@ -318,9 +337,7 @@ bool save_script_binary(const ScriptHandle &script, cryptonote::blobdata &blob,
if (blobs)
{
blobs->clear();
replace_blob_with_hash(copy.icon, *blobs);
for (auto &s: copy.states)
replace_blob_with_hash(s.image, *blobs);
foreach_blob_candidate(&copy, [blobs](std::string &s){ replace_blob_with_hash(s, *blobs); return true; });
}
blob = cryptonote::t_serializable_object_to_blob(const_cast<Script&>(copy));
return true;
@ -328,15 +345,21 @@ bool save_script_binary(const ScriptHandle &script, cryptonote::blobdata &blob,
static bool load_contents_if_reference(std::string &dst)
{
if (dst.empty() || dst[0] != '@')
if (dst.empty())
return true;
const std::string filename = dst.substr(1);
const bool raw = starting_with(dst, "@raw:");
const bool hex = starting_with(dst, "@hex:");
if (!raw && !hex)
return true;
const std::string filename = dst.substr(5);
std::string str;
if (!epee::file_io_utils::load_file_to_string(filename, str))
{
MERROR("File not found: " << filename);
return false;
}
if (hex)
str = epee::string_tools::buff_to_hex_nodelimer(str);
dst = std::move(str);
return true;
}
@ -349,18 +372,7 @@ static bool set_unresolved_reference(std::string &unresolved_reference, const st
bool resolve_references(ScriptHandle &s, std::string &unresolved_reference)
{
if (!load_contents_if_reference(s.script->icon))
return set_unresolved_reference(unresolved_reference, s.script->icon);
for (auto &state: s.script->states)
{
if (!load_contents_if_reference(state.image))
return set_unresolved_reference(unresolved_reference, state.image);
if (!load_contents_if_reference(state.ui))
return set_unresolved_reference(unresolved_reference, state.ui);
if (!load_contents_if_reference(state.choices_ui))
return set_unresolved_reference(unresolved_reference, state.choices_ui);
}
return true;
return foreach_blob_candidate(s.script, [&unresolved_reference](std::string &s){ if (!load_contents_if_reference(s)) return set_unresolved_reference(unresolved_reference, s); return true; });
}
static bool load_contents_if_reference(const cryptonote::BlockchainDB &db, std::string &s)
@ -378,53 +390,49 @@ static bool load_contents_if_reference(const cryptonote::BlockchainDB &db, std::
bool resolve_references(const cryptonote::BlockchainDB &db, ScriptHandle &s)
{
if (!load_contents_if_reference(db, s.script->icon))
return false;
for (auto &state: s.script->states)
{
if (!load_contents_if_reference(db, state.image))
return false;
}
return true;
return foreach_blob_candidate(s.script, [&db](std::string &s){ return load_contents_if_reference(db, s); });
}
bool resolve_reference(const cryptonote::BlockchainDB &db, std::string &s)
{
return load_contents_if_reference(db, s);
}
static bool get_contents_if_reference(std::string &s, std::vector<cryptonote::blobdata> &references)
{
if (s.empty() || s[0] != '@')
if (s.empty())
return true;
const std::string filename = s.substr(1);
const bool raw = starting_with(s, "@raw:");
const bool hex = starting_with(s, "@hex:");
if (!raw && !hex)
return true;
const std::string filename = s.substr(5);
std::string str;
if (!epee::file_io_utils::load_file_to_string(filename, str))
{
MERROR("File not found: " << filename);
return false;
}
if (hex)
str = epee::string_tools::buff_to_hex_nodelimer(str);
const crypto::hash hash = get_blob_hash(str);
s.assign((const char*)&hash, sizeof(hash));
references.push_back(std::move(str));
if (std::find(references.begin(), references.end(), str) == references.end())
references.push_back(std::move(str));
return true;
}
bool get_references(ScriptHandle &s, std::vector<cryptonote::blobdata> &references, std::string &unresolved_reference)
{
if (!get_contents_if_reference(s.script->icon, references))
return set_unresolved_reference(unresolved_reference, s.script->icon);
for (auto &state: s.script->states)
{
if (!get_contents_if_reference(state.image, references))
return set_unresolved_reference(unresolved_reference, state.image);
}
return true;
Script copy = *s.script;
return foreach_blob_candidate(&copy, [&references, &unresolved_reference](std::string &s){ if (!get_contents_if_reference(s, references)) return set_unresolved_reference(unresolved_reference, s); return true; });
}
bool is_blob_used(const ScriptHandle &s, const crypto::hash hash)
bool is_blob_used(const ScriptHandle &s, const crypto::hash &hash)
{
if (s.script->icon.size() == 32 && *(const crypto::hash*)s.script->icon.data() == hash)
return true;
for (auto &state: s.script->states)
if (state.image.size() == 32 && *(const crypto::hash*)state.image.data() == hash)
return true;
return false;
bool used = false;
foreach_blob_candidate(s.script, [&hash, &used](std::string &s){ if (s.size() == 32 && *(const crypto::hash*)s.data() == hash) used = true; return true; });
return used;
}
uint32_t get_script_owner(const ScriptHandle &script)

View File

@ -136,8 +136,9 @@ bool save_script_native(const ScriptHandle &s, std::string &source, uint32_t fla
crypto::hash get_blob_hash(const cryptonote::blobdata &blob);
bool resolve_references(ScriptHandle &s, std::string &unresolved_reference);
bool resolve_references(const cryptonote::BlockchainDB &db, ScriptHandle &s);
bool resolve_reference(const cryptonote::BlockchainDB &db, std::string &s);
bool get_references(ScriptHandle &s, std::vector<cryptonote::blobdata> &references, std::string &unresolved_reference);
bool is_blob_used(const ScriptHandle &s, const crypto::hash hash);
bool is_blob_used(const ScriptHandle &s, const crypto::hash &hash);
uint32_t get_script_owner(const ScriptHandle &script);
std::string get_script_name(const ScriptHandle &script);
std::string get_script_name(const std::string &blob);

View File

@ -181,7 +181,7 @@ state_content: TEXT STRING { set_partial_state_state_text(state, $2); }
| UI STRING { set_partial_state_state_ui(state, $2); }
| CHOICES UI STRING { set_partial_state_state_choices_ui(state, $3); }
| CHOICE '{' choice_contents '}' { set_partial_state_choice(state); }
| ACTIONS '{' actions '}' { set_partial_state_state_init_actions(state); }
| INIT '{' actions '}' { set_partial_state_state_init_actions(state); }
;
choice_contents: choice_contents choice_content

View File

@ -79,10 +79,13 @@ void UINewScriptDialog::Parse(const std::shared_ptr<GameWallet> &w)
okWidget->SetState(WIDGET_STATE_DISABLED, false);
validContainer->SetValue(1);
std::string blob;
blob.clear();
shared_blobs.clear();
new_blobs.clear();
std::vector<cryptonote::blobdata> blobs;
cc::script::save_script_binary(script, blob);
const size_t size = blob.size();
std::vector<cryptonote::blobdata> blobs;
cc::script::save_script_binary(script, blob, &blobs);
std::vector<crypto::hash> hashes;
hashes.reserve(blob.size());
@ -93,8 +96,18 @@ void UINewScriptDialog::Parse(const std::shared_ptr<GameWallet> &w)
{
size_t marginal_size = size;
for (size_t i = 0; i < refs.size(); ++i)
{
marginal_size += sizeof(crypto::hash);
if (refs[i] > 0)
marginal_size -= blobs[i].size() - sizeof(crypto::hash);
{
marginal_size -= blobs[i].size();
shared_blobs.push_back(blobs[i]);
}
else
{
new_blobs.push_back(blobs[i]);
}
}
const std::string msg = " - " + std::to_string(marginal_size) + " bytes (+" + std::to_string(size - marginal_size) + " shared)";
sizeWidget->SetText(msg.c_str());
}
@ -153,11 +166,8 @@ void UINewScriptDialog::HandleConfirmation(StringHash eventType, VariantMap& eve
return;
}
std::string blob;
cc::script::save_script_binary(script, blob);
VariantMap& newEventData = GetEventDataMap();
newEventData[NewScriptOkayed::P_BLOBS] = &references;
newEventData[NewScriptOkayed::P_BLOBS] = &new_blobs;
newEventData[NewScriptOkayed::P_SCRIPT] = String(blob.data(), blob.size());
SendEvent(E_NEW_SCRIPT_OKAYED, newEventData);

View File

@ -59,6 +59,10 @@ private:
tb::TBButton *okWidget;
tb::TBToggleContainer *validContainer;
tb::TBToggleContainer *editContainer;
cryptonote::blobdata blob;
std::vector<cryptonote::blobdata> shared_blobs;
std::vector<cryptonote::blobdata> new_blobs;
};
#endif

View File

@ -269,12 +269,8 @@ void UIScriptsDialog::FillScriptList(const std::shared_ptr<GameWallet> &w)
}
}
void UIScriptsDialog::ProcessOverrides(tb::TBWidget *w, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides)
void UIScriptsDialog::ProcessOverrides(std::string &s, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides)
{
if (!w)
return;
std::string s = w->GetText().CStr();
for (const auto &e: overrides)
{
if (std::get<0>(e) == cc::script::override_string)
@ -283,6 +279,15 @@ void UIScriptsDialog::ProcessOverrides(tb::TBWidget *w, const std::vector<std::t
boost::replace_all(s, code, std::get<2>(e));
}
}
}
void UIScriptsDialog::ProcessOverrides(tb::TBWidget *w, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides)
{
if (!w)
return;
std::string s = w->GetText().CStr();
ProcessOverrides(s, overrides);
w->SetText(s.c_str());
for (w = w->GetFirstChild(); w; w = w->GetNext())
@ -331,7 +336,11 @@ void UIScriptsDialog::FillChoiceList(const std::shared_ptr<GameWallet> &w)
if (!image.empty())
{
TBImageWidget *imageWidget = new TBImageWidget();
imageWidget->SetImage(image.data(), image.size());
std::string decoded_image;
if (epee::string_tools::parse_hexstr_to_binbuff(image, decoded_image))
imageWidget->SetImage(decoded_image.data(), decoded_image.size());
else
printf("Failed to decode image\n");
choicesLayout->AddChild(imageWidget);
}
@ -352,6 +361,7 @@ void UIScriptsDialog::FillChoiceList(const std::shared_ptr<GameWallet> &w)
if (!ui.empty())
{
ProcessOverrides(ui, overrides);
if (!g_widgets_reader->LoadData(choicesLayout, ui.c_str()))
{
new MessageBox(context_, "Failed to parse UI");
@ -361,15 +371,16 @@ void UIScriptsDialog::FillChoiceList(const std::shared_ptr<GameWallet> &w)
if (!choices_ui.empty())
{
ProcessOverrides(choices_ui, overrides);
if (!g_widgets_reader->LoadData(choicesLayout, choices_ui.c_str()))
{
new MessageBox(context_, "Failed to parse choices UI");
return;
}
}
else
{
if (choices.empty())
if (choices.empty() && choices_ui.empty())
{
TBButton *button = new TBButton();
button->data.SetInt(0);
@ -377,8 +388,10 @@ void UIScriptsDialog::FillChoiceList(const std::shared_ptr<GameWallet> &w)
button->SetText("End");
choicesLayout->AddChild(button);
}
else for (const auto &e: choices)
for (const auto &e: choices)
{
if (std::get<1>(e).empty())
continue;
TBButton *button = new TBButton();
button->data.SetInt(std::get<0>(e));
button->SetID(TBIDC("choice"));
@ -387,7 +400,6 @@ void UIScriptsDialog::FillChoiceList(const std::shared_ptr<GameWallet> &w)
}
}
ProcessOverrides(choicesLayout, overrides);
ProcessControlCodes(choicesLayout);
ProcessWidgetValues(choicesLayout);
}

View File

@ -59,6 +59,7 @@ private:
void FillScriptList(const std::shared_ptr<GameWallet> &w);
void FillChoiceList(const std::shared_ptr<GameWallet> &w);
void UpdateDetailsVisibility();
void ProcessOverrides(std::string &s, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides);
void ProcessOverrides(tb::TBWidget *w, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides);
void ProcessControlCodes(tb::TBWidget *w);
void ProcessWidgetValues(tb::TBWidget *w);

View File

@ -5109,6 +5109,21 @@ namespace cryptonote
res.desc = std::move(sd.desc);
res.owner = sd.owner;
res.is_public = sd.is_public;
cc::script::ScriptHandle script;
if (!cc::script::load_script_binary(sd.blob, script))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Error parsing script";
return false;
}
if (!cc::script::resolve_references(db, script))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Error reconstructing script";
return false;
}
res.blob = epee::string_tools::buff_to_hex_nodelimer(sd.blob);
}
catch (const std::exception &e)
@ -5145,15 +5160,25 @@ namespace cryptonote
error_resp.message = "Error parsing script";
return false;
}
if (!cc::script::resolve_references(db, script))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Error reconstructing script";
return false;
}
cc::BlockchainStateProxy proxy(db);
std::vector<std::tuple<uint32_t, std::string, std::string>> choices;
if (!cc::script::get_state(proxy, script, req.account, req.state, req.city, res.ui, res.text, res.image, choices, res.choices_ui))
std::string image, ui, choices_ui;
if (!cc::script::get_state(proxy, script, req.account, req.state, req.city, ui, res.text, image, choices, choices_ui))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Error getting script state";
return false;
}
res.image = epee::string_tools::buff_to_hex_nodelimer(image);
res.ui = epee::string_tools::buff_to_hex_nodelimer(ui);
res.choices_ui = epee::string_tools::buff_to_hex_nodelimer(choices_ui);
res.choices.reserve(choices.size());
for (const auto &e: choices)
res.choices.push_back({std::get<0>(e), std::get<1>(e), std::get<2>(e)});

View File

@ -838,15 +838,18 @@ bool wallet2::get_cc_script_state(uint32_t account, uint32_t script, uint32_t st
if (!r || res.status != CORE_RPC_STATUS_OK)
return false;
ui = std::move(res.ui);
if (!res.ui.empty() && !epee::string_tools::parse_hexstr_to_binbuff(res.ui, ui))
return false;
if (!res.choices_ui.empty() && !epee::string_tools::parse_hexstr_to_binbuff(res.choices_ui, choices_ui))
return false;
if (!res.image.empty() && !epee::string_tools::parse_hexstr_to_binbuff(res.image, image))
return false;
text = std::move(res.text);
image = std::move(res.image);
choices.resize(res.choices.size());
for (size_t i = 0; i < res.choices.size(); ++i)
{
choices[i] = std::make_tuple(res.choices[i].id, std::move(res.choices[i].text), std::move(res.choices[i].selected_text));
}
choices_ui = std::move(res.choices_ui);
overrides.resize(res.overrides.size());
for (size_t i = 0; i < res.overrides.size(); ++i)
overrides[i] = std::make_tuple(res.overrides[i].type, res.overrides[i].idx, std::move(res.overrides[i].str));