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

require names be valid Antelope names when parsing from JSON #37

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Binary <> JSON conversion using ABIs. Compatible with languages which can interface to C; see [src/abieos.h](src/abieos.h).

Alpha release. Feedback requested.
## Important or breaking changes
`main` branch is considered stable. No explicit releases are made. Important or possibly breaking changes are listed below. View linked PR for additional details.
* **Janurary 2025 via PR [#37](https://github.com/AntelopeIO/abieos/pull/37)**: When converting from JSON, `name`s must be valid Antelope names (no more than 13 characters, a-z.12345 only, etc). Most use cases are unaffected by this change. Consuming state_history's JSON ABI via state_history's websocket will require a minor change in user code.

## Packing transactions

Expand Down
71 changes: 71 additions & 0 deletions include/eosio/abi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "time.hpp"
#include "bytes.hpp"
#include "asset.hpp"
#include "murmur.hpp"

namespace eosio {

Expand Down Expand Up @@ -169,9 +170,79 @@ struct abi_def {
might_not_exist<std::vector<action_result_def>> action_results{};
};

/* Allows for table names that are not Antelope names (longer than 13 chars, invalid chars, etc) when loading ABI from JSON. These
* invalid names are transformed in to a valid Antelope name via a hash.
* This is intended strictly to ingest state_history's non-compliant ABI sent over the state_history websocket.
*/
struct abi_def_nonstrict_table_name : abi_def {};

EOSIO_REFLECT(abi_def, version, types, structs, actions, tables, ricardian_clauses, error_messages, abi_extensions,
variants, action_results);

template <typename S>
void hashed_name_for_name_from_json(name& nm, S& stream) {
const auto s = stream.get_string();
const auto n = try_string_to_name_strict(s);
if(n)
nm = name(n.value());
else
nm = name(murmur64(s.data(), s.size()));
}

template <typename T, typename S>
void hashed_name_for_name_from_json(T&, S&) {
check( false, "should be unreachable" );
}

template <typename S>
void table_def_array_nonstrict_name_from_json(std::vector<table_def>& tbl_defs, S& stream) {
stream.get_start_array();
while (true) {
auto t = stream.peek_token();
if (t.get().type == json_token_type::type_end_array)
break;
auto& tbl_def = tbl_defs.emplace_back();
from_json_object(stream, [&](std::string_view key) {
bool found = false;
eosio::for_each_field<table_def>([&](std::string_view member_name, auto member) {
if (!found && key == member_name) {
if (member_name == "name")
hashed_name_for_name_from_json(member(&tbl_def), stream);
else
from_json(member(&tbl_def), stream);
found = true;
}
});
if (!found)
from_json_skip_value(stream);
});
}
stream.get_end_array();
}

template <typename T, typename S>
void table_def_array_nonstrict_name_from_json(T&, S&) {
check( false, "should be unreachable" );
}

template <typename S>
void from_json(abi_def_nonstrict_table_name& obj, S& stream) {
from_json_object(stream, [&](std::string_view key) {
bool found = false;
eosio::for_each_field<abi_def_nonstrict_table_name>([&](std::string_view member_name, auto member) {
if (!found && key == member_name) {
if (member_name == "tables")
table_def_array_nonstrict_name_from_json(member(&obj), stream);
else
from_json(member(&obj), stream);
found = true;
}
});
if (!found)
from_json_skip_value(stream);
});
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am disappointed with how this turned out; the code just mostly just a copy paste job from existing struct & vector from_json but it's still a copy paste job.

Normally how I'd want to handle this is by doing something like

template<typename TableNameType>
struct abi_def_template { /* ... */ };
using abi_def = abi_def_template<eosio::name>;
using abi_def_nonstrict_table_name = abi_def_template<eosio::hashed_name>;

but the problem is this won't allow abi_def_nonstrict_table_name to be passed off to eosio::convert(). I could go and make eosio::convert() take a templated abi_def_template but I was concerned the changes would be too sprawling vs something isolated here.

struct abi_type;

struct abi_field {
Expand Down
13 changes: 1 addition & 12 deletions include/eosio/name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "check.hpp"
#include "operators.hpp"
#include "reflection.hpp"
#include "murmur.hpp"
#include <string>

namespace eosio {
Expand Down Expand Up @@ -140,21 +139,12 @@ struct name {
}
};

// TODO this seems weird and the name is misleading
// and I don't think this has ever been truly constexpr
inline constexpr uint64_t hash_name( std::string_view str ) {
auto r = try_string_to_name_strict(str);
if( r ) return r.value();
return murmur64( str.data(), str.size() );
}

EOSIO_REFLECT(name, value);
EOSIO_COMPARE(name);

template <typename S>
void from_json(name& obj, S& stream) {
auto r = stream.get_string();
obj = name(hash_name(r));
obj = name(string_to_name_strict(stream.get_string()));
}

template <typename S>
Expand All @@ -170,7 +160,6 @@ inline namespace literals {
template <typename T, T... Str>
inline constexpr name operator""_n() {
return name(string_to_name_strict<Str...>()); }
inline constexpr name operator""_h(const char* s, size_t) { return name( hash_name(s) ); }
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
Expand Down
48 changes: 43 additions & 5 deletions src/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,25 @@ void check_error(abieos_context* context, const std::string& s, F f) {
check_except(s, [&] { check_context(context, f()); });
}

/* abieos' C interface, used by check_types() below, uses abi_def which disallows the invalid
* table names SHIP's ABI contains. For purposes of this test, scrub all the tables from the ABI
* before loading it; they aren't used.
* The check_ship_abi_load() test checks abi_def vs abi_def_nonstrict_table_name behavior on the unmodified
* ship.abi.cpp contents.
*/
rapidjson::StringBuffer scrub_shipabi_tables(const char* const s) {
rapidjson::Document document;
document.Parse(s);
check(document.IsObject(), "unexpected ship abi");
check(document["tables"].IsArray(), "unexpected ship abi");
document["tables"].GetArray().Clear();

rapidjson::StringBuffer buff;
rapidjson::Writer<rapidjson::StringBuffer> writer(buff);
document.Accept(writer);
return buff;
}

void check_types() {
auto context = check(abieos_create());
auto token = check_context(context, abieos_string_to_name(context, "eosio.token"));
Expand All @@ -582,7 +601,7 @@ void check_types() {
auto testKvAbiName = check_context(context, abieos_string_to_name(context, "testkv.abi"));
check_context(context, abieos_set_abi(context, 0, transactionAbi));
check_context(context, abieos_set_abi(context, 1, packedTransactionAbi));
check_context(context, abieos_set_abi(context, 2, state_history_plugin_abi));
check_context(context, abieos_set_abi(context, 2, scrub_shipabi_tables(state_history_plugin_abi).GetString()));
check_context(context, abieos_set_abi_hex(context, token, tokenHexAbi));
check_context(context, abieos_set_abi(context, testAbiName, testAbi));
check_context(context, abieos_set_abi_hex(context, testHexAbiName, testHexAbi));
Expand Down Expand Up @@ -830,9 +849,8 @@ void check_types() {
check_type(context, 0, "name", R"("ab.cd.ef.1234")");
check_type(context, 0, "name", R"("..ab.cd.ef..")", R"("..ab.cd.ef")");
check_type(context, 0, "name", R"("zzzzzzzzzzzz")");
// todo: should json conversion fall back to hash? reenable this error?
// check_error(context, "thirteenth character in name cannot be a letter that comes after j",
// [&] { return abieos_json_to_bin(context, 0, "name", R"("zzzzzzzzzzzzz")"); });
check_error(context, "thirteenth character in name cannot be a letter that comes after j",
[&] { return abieos_json_to_bin(context, 0, "name", R"("zzzzzzzzzzzzz")"); });
check_error(context, "expected string containing name",
[&] { return abieos_json_to_bin(context, 0, "name", "true"); });
check_type(context, 0, "bytes", R"("")");
Expand Down Expand Up @@ -1241,10 +1259,30 @@ void check_types() {
abieos_destroy(context);
}

void check_ship_abi_load() {
{
eosio::abi_def def;
std::string ship_abi_copy = state_history_plugin_abi;
eosio::json_token_stream stream(ship_abi_copy.data());
try {
from_json(def, stream);
throw std::runtime_error("abi_def shouldn't have loaded ship ABI");
} catch (std::exception& e) {}
}
{
eosio::abi_def_nonstrict_table_name def;
std::string ship_abi_copy = state_history_plugin_abi;
eosio::json_token_stream stream(ship_abi_copy.data());
from_json(def, stream);
}
}

int main() {
try {
check_types();
printf("\ncheck_types ok\n\n");
printf("\ncheck_types ok\n");
check_ship_abi_load();
printf("\ncheck_ship_abi_load ok\n\n");
return 0;
} catch (std::exception& e) {
printf("error: %s\n", e.what());
Expand Down