Merge branch 'develop' into 'beatrice'

Merge develop to beatrice 2022-12

See merge request PBSA/peerplays!193
This commit is contained in:
Bobinson K B 2023-01-03 07:33:40 +00:00
commit 6f472d3d3b
26 changed files with 991 additions and 477 deletions

View file

@ -11,15 +11,14 @@ RUN \
apt-utils \
autoconf \
bash \
bison \
build-essential \
ca-certificates \
cmake \
dnsutils \
doxygen \
expect \
flex \
git \
graphviz \
libboost-all-dev \
libbz2-dev \
libcurl4-openssl-dev \
libncurses-dev \
@ -35,7 +34,6 @@ RUN \
ntp \
openssh-server \
pkg-config \
perl \
python3 \
python3-jinja2 \
sudo \
@ -53,6 +51,31 @@ RUN echo 'peerplays:peerplays' | chpasswd
# SSH
EXPOSE 22
#===============================================================================
# Boost setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \
tar -xzvf boost_1_72_0.tar.gz boost_1_72_0 && \
cd boost_1_72_0/ && \
./bootstrap.sh && \
./b2 install
#===============================================================================
# cmake setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
wget -c 'https://cmake.org/files/v3.23/cmake-3.23.1-linux-x86_64.sh' -O cmake-3.23.1-linux-x86_64.sh && \
chmod 755 ./cmake-3.23.1-linux-x86_64.sh && \
./cmake-3.23.1-linux-x86_64.sh --prefix=/usr/ --skip-license && \
cmake --version
#===============================================================================
# libzmq setup
#===============================================================================
@ -85,6 +108,37 @@ RUN \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Doxygen setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
sudo apt install -y bison flex && \
wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \
tar -xvf Release_1_8_17.tar.gz && \
cd doxygen-Release_1_8_17 && \
mkdir build && \
cd build && \
cmake .. && \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Perl setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \
tar -xvf v5.30.0.tar.gz && \
cd perl5-5.30.0 && \
./Configure -des && \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Peerplays setup
#===============================================================================
@ -106,6 +160,9 @@ ADD . peerplays
# Configure Peerplays
RUN \
cd peerplays && \
git submodule update --init --recursive && \
git symbolic-ref --short HEAD && \
git log --oneline -n 5 && \
mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release ..

View file

@ -11,11 +11,12 @@ RUN \
apt-utils \
autoconf \
bash \
bison \
build-essential \
ca-certificates \
dnsutils \
doxygen \
expect \
flex \
git \
graphviz \
libbz2-dev \
@ -33,7 +34,6 @@ RUN \
ntp \
openssh-server \
pkg-config \
perl \
python3 \
python3-jinja2 \
sudo \
@ -58,9 +58,9 @@ EXPOSE 22
WORKDIR /home/peerplays/
RUN \
wget -c 'https://boostorg.jfrog.io/artifactory/main/release/1.71.0/source/boost_1_71_0.tar.bz2' -O boost_1_71_0.tar.bz2 && \
tar xjf boost_1_71_0.tar.bz2 && \
cd boost_1_71_0/ && \
wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \
tar -xzvf boost_1_72_0.tar.gz boost_1_72_0 && \
cd boost_1_72_0/ && \
./bootstrap.sh && \
./b2 install
@ -108,6 +108,37 @@ RUN \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Doxygen setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
sudo apt install -y bison flex && \
wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \
tar -xvf Release_1_8_17.tar.gz && \
cd doxygen-Release_1_8_17 && \
mkdir build && \
cd build && \
cmake .. && \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Perl setup
#===============================================================================
WORKDIR /home/peerplays/
RUN \
wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \
tar -xvf v5.30.0.tar.gz && \
cd perl5-5.30.0 && \
./Configure -des && \
make -j$(nproc) install && \
ldconfig
#===============================================================================
# Peerplays setup
#===============================================================================
@ -129,6 +160,9 @@ ADD . peerplays
# Configure Peerplays
RUN \
cd peerplays && \
git submodule update --init --recursive && \
git symbolic-ref --short HEAD && \
git log --oneline -n 5 && \
mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release ..

View file

@ -71,6 +71,17 @@ std::string object_id_to_string(object_id_type id) {
return object_id;
}
signed_block_with_info::signed_block_with_info(){};
signed_block_with_info::signed_block_with_info(const signed_block &block) :
signed_block(block) {
block_id = id();
signing_key = signee();
transaction_ids.reserve(transactions.size());
for (const processed_transaction &tx : transactions)
transaction_ids.push_back(tx.id());
}
class database_api_impl : public std::enable_shared_from_this<database_api_impl> {
public:
database_api_impl(graphene::chain::database &db);
@ -89,6 +100,7 @@ public:
optional<block_header> get_block_header(uint32_t block_num) const;
map<uint32_t, optional<block_header>> get_block_header_batch(const vector<uint32_t> block_nums) const;
optional<signed_block> get_block(uint32_t block_num) const;
optional<signed_block_with_info> get_block2(uint32_t block_num) const;
vector<optional<signed_block>> get_blocks(uint32_t block_num_from, uint32_t block_num_to) const;
processed_transaction get_transaction(uint32_t block_num, uint32_t trx_in_block) const;
@ -269,8 +281,9 @@ public:
uint64_t nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const;
nft_object nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const;
nft_object nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const;
vector<nft_object> nft_get_all_tokens() const;
vector<nft_object> nft_get_tokens_by_owner(const account_id_type owner) const;
vector<nft_object> nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const;
vector<nft_object> nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const;
vector<nft_metadata_object> nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const;
// Marketplace
vector<offer_object> list_offers(const offer_id_type lower_id, uint32_t limit) const;
@ -291,6 +304,7 @@ public:
uint32_t api_limit_get_limit_orders_by_account = 101;
uint32_t api_limit_get_order_book = 50;
uint32_t api_limit_all_offers_count = 100;
uint32_t api_limit_nft_tokens = 100;
uint32_t api_limit_lookup_accounts = 1000;
uint32_t api_limit_lookup_witness_accounts = 1000;
uint32_t api_limit_lookup_committee_member_accounts = 1000;
@ -530,6 +544,17 @@ optional<signed_block> database_api_impl::get_block(uint32_t block_num) const {
return _db.fetch_block_by_number(block_num);
}
optional<signed_block_with_info> database_api::get_block2(uint32_t block_num) const {
return my->get_block2(block_num);
}
optional<signed_block_with_info> database_api_impl::get_block2(uint32_t block_num) const {
auto result = _db.fetch_block_by_number(block_num);
if (result)
return signed_block_with_info(*result);
return {};
}
vector<optional<signed_block>> database_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to) const {
return my->get_blocks(block_num_from, block_num_to);
}
@ -3102,30 +3127,61 @@ nft_object database_api_impl::nft_token_of_owner_by_index(const nft_metadata_id_
return {};
}
vector<nft_object> database_api::nft_get_all_tokens() const {
return my->nft_get_all_tokens();
vector<nft_object> database_api::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const {
return my->nft_get_all_tokens(lower_id, limit);
}
vector<nft_object> database_api_impl::nft_get_all_tokens() const {
vector<nft_object> database_api_impl::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const {
FC_ASSERT(limit <= api_limit_nft_tokens,
"Number of queried nft tokens can not be greater than ${configured_limit}",
("configured_limit", api_limit_nft_tokens));
const auto &idx_nft = _db.get_index_type<nft_index>().indices().get<by_id>();
vector<nft_object> result;
for (auto itr = idx_nft.begin(); itr != idx_nft.end(); ++itr) {
result.push_back(*itr);
}
result.reserve(limit);
auto itr = idx_nft.lower_bound(lower_id);
while (limit-- && itr != idx_nft.end())
result.emplace_back(*itr++);
return result;
}
vector<nft_object> database_api::nft_get_tokens_by_owner(const account_id_type owner) const {
return my->nft_get_tokens_by_owner(owner);
vector<nft_object> database_api::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const {
return my->nft_get_tokens_by_owner(owner, lower_id, limit);
}
vector<nft_object> database_api_impl::nft_get_tokens_by_owner(const account_id_type owner) const {
vector<nft_object> database_api_impl::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const {
FC_ASSERT(limit <= api_limit_nft_tokens,
"Number of queried nft tokens can not be greater than ${configured_limit}",
("configured_limit", api_limit_nft_tokens));
const auto &idx_nft = _db.get_index_type<nft_index>().indices().get<by_owner>();
auto idx_nft_range = idx_nft.equal_range(owner);
vector<nft_object> result;
for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) {
result.push_back(*itr);
}
result.reserve(limit);
auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_object &obj) {
return !(obj.id.instance() < lower_id.instance);
});
while (limit-- && itr != idx_nft_range.second)
result.emplace_back(*itr++);
return result;
}
vector<nft_metadata_object> database_api::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const {
return my->nft_get_metadata_by_owner(owner, lower_id, limit);
}
vector<nft_metadata_object> database_api_impl::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const {
FC_ASSERT(limit <= api_limit_nft_tokens,
"Number of queried nft metadata objects can not be greater than ${configured_limit}",
("configured_limit", api_limit_nft_tokens));
const auto &idx_nft = _db.get_index_type<nft_metadata_index>().indices().get<by_owner>();
auto idx_nft_range = idx_nft.equal_range(owner);
vector<nft_metadata_object> result;
result.reserve(limit);
auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_metadata_object &obj) {
return !(obj.id.instance() < lower_id.instance);
});
while (limit-- && itr != idx_nft_range.second)
result.emplace_back(*itr++);
return result;
}

View file

@ -82,6 +82,15 @@ using namespace std;
class database_api_impl;
struct signed_block_with_info : public signed_block {
signed_block_with_info();
signed_block_with_info(const signed_block &block);
signed_block_with_info(const signed_block_with_info &block) = default;
block_id_type block_id;
public_key_type signing_key;
vector<transaction_id_type> transaction_ids;
};
struct order {
double price;
double quote;
@ -202,6 +211,13 @@ public:
*/
optional<signed_block> get_block(uint32_t block_num) const;
/**
* @brief Retrieve a full, signed block, with some extra info
* @param block_num Height of the block to be returned
* @return the referenced block, or null if no matching block was found
*/
optional<signed_block_with_info> get_block2(uint32_t block_num) const;
/**
* @brief Retrieve a list of signed blocks
* @param block_num_from start
@ -1027,14 +1043,25 @@ public:
* @brief Returns list of all available NTF's
* @return List of all available NFT's
*/
vector<nft_object> nft_get_all_tokens() const;
vector<nft_object> nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const;
/**
* @brief Returns NFT's owned by owner
* @param owner NFT owner
* @param lower_id ID of the first NFT to return
* @param limit Maximum number of results to return
* @return List of NFT owned by owner
*/
vector<nft_object> nft_get_tokens_by_owner(const account_id_type owner) const;
vector<nft_object> nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const;
/**
* @brief Returns NFT metadata owned by owner
* @param owner NFT owner
* @param lower_id ID of the first NFT metadata to return
* @param limit Maximum number of results to return
* @return List of NFT owned by owner
*/
vector<nft_metadata_object> nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const;
//////////////////
// MARKET PLACE //
@ -1064,6 +1091,8 @@ extern template class fc::api<graphene::app::database_api>;
// clang-format off
FC_REFLECT_DERIVED(graphene::app::signed_block_with_info, (graphene::chain::signed_block), (block_id)(signing_key)(transaction_ids));
FC_REFLECT(graphene::app::order, (price)(quote)(base));
FC_REFLECT(graphene::app::order_book, (base)(quote)(bids)(asks));
FC_REFLECT(graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume));
@ -1086,6 +1115,7 @@ FC_API(graphene::app::database_api,
(get_block_header)
(get_block_header_batch)
(get_block)
(get_block2)
(get_blocks)
(get_transaction)
(get_recent_transaction_by_id)
@ -1249,6 +1279,7 @@ FC_API(graphene::app::database_api,
(nft_token_of_owner_by_index)
(nft_get_all_tokens)
(nft_get_tokens_by_owner)
(nft_get_metadata_by_owner)
// Marketplace
(list_offers)

View file

@ -236,8 +236,10 @@ std::set<son_id_type> database::get_sons_to_be_deregistered()
// TODO : We need to add a function that returns if we can deregister SON
// i.e. with introduction of PW code, we have to make a decision if the SON
// is needed for release of funds from the PW
if (head_block_time() - stats.last_active_timestamp.at(sidechain) < fc::seconds(get_global_properties().parameters.son_deregister_time())) {
need_to_be_deregistered = false;
if(stats.last_active_timestamp.contains(sidechain)) {
if (head_block_time() - stats.last_active_timestamp.at(sidechain) < fc::seconds(get_global_properties().parameters.son_deregister_time())) {
need_to_be_deregistered = false;
}
}
}
}
@ -311,9 +313,10 @@ bool database::is_son_dereg_valid( son_id_type son_id )
if(status_son_dereg_valid)
{
if(head_block_time() - son->statistics(*this).last_active_timestamp.at(sidechain) < fc::seconds(get_global_properties().parameters.son_deregister_time()))
{
status_son_dereg_valid = false;
if(son->statistics(*this).last_active_timestamp.contains(sidechain)) {
if (head_block_time() - son->statistics(*this).last_active_timestamp.at(sidechain) < fc::seconds(get_global_properties().parameters.son_deregister_time())) {
status_son_dereg_valid = false;
}
}
}
}

View file

@ -310,7 +310,10 @@ void database::update_son_metrics(const flat_map<sidechain_type, vector<son_info
if (is_active_son) {
_stats.total_voted_time[sidechain] = _stats.total_voted_time[sidechain] + get_global_properties().parameters.maintenance_interval;
}
_stats.total_downtime[sidechain] += _stats.current_interval_downtime[sidechain];
if(!_stats.current_interval_downtime.contains(sidechain))
_stats.current_interval_downtime[sidechain] = 0;
_stats.total_downtime[sidechain] += _stats.current_interval_downtime.at(sidechain);
_stats.current_interval_downtime[sidechain] = 0;
_stats.sidechain_txs_reported[sidechain] = 0;
});

View file

@ -130,6 +130,9 @@ namespace graphene { namespace chain {
std::greater< uint32_t >,
std::greater< object_id_type >
>
>,
ordered_non_unique< tag<by_owner>,
member<nft_metadata_object, account_id_type, &nft_metadata_object::owner>
>
>
>;

View file

@ -208,7 +208,7 @@ object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation&
if (itr->statuses.at(sidechain) == son_status::in_maintenance) {
db().modify(itr->statistics(db()), [&](son_statistics_object &sso) {
sso.current_interval_downtime[sidechain] += op.ts.sec_since_epoch() - sso.last_down_timestamp.at(sidechain).sec_since_epoch();
sso.current_interval_downtime[sidechain] += op.ts.sec_since_epoch() - (sso.last_down_timestamp.contains(sidechain) ? sso.last_down_timestamp.at(sidechain).sec_since_epoch() : op.ts.sec_since_epoch());
sso.last_active_timestamp[sidechain] = op.ts;
});
@ -245,7 +245,8 @@ void_result son_report_down_evaluator::do_evaluate(const son_report_down_operati
}
FC_ASSERT(status_need_to_report_down, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down");
for(const auto& active_sidechain_type : active_sidechain_types) {
FC_ASSERT(op.down_ts >= stats.last_active_timestamp.at(active_sidechain_type), "sidechain = ${sidechain} down_ts should be greater than last_active_timestamp", ("sidechain", active_sidechain_type));
if(stats.last_active_timestamp.contains(active_sidechain_type))
FC_ASSERT(op.down_ts >= stats.last_active_timestamp.at(active_sidechain_type), "sidechain = ${sidechain} down_ts should be greater than last_active_timestamp", ("sidechain", active_sidechain_type));
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -2,7 +2,6 @@
#include <boost/algorithm/hex.hpp>
#include <boost/format.hpp>
#include <stdlib.h>
#include <graphene/peerplays_sidechain/ethereum/utils.hpp>
@ -19,12 +18,16 @@ std::string base_encoder::encode_address(const std::string &value) {
std::string base_encoder::encode_string(const std::string &value) {
std::string data = (boost::format("%x") % boost::io::group(std::setw(64), std::setfill('0'), value.size())).str();
data += boost::algorithm::hex(value) + std::string((64 - value.size() * 2 % 64), '0');
data += boost::algorithm::hex(value);
if (value.size() % 32 != 0) {
data += std::string((64 - value.size() * 2 % 64), '0');
}
return data;
}
//! update_owners_encoder
std::string update_owners_encoder::encode(const std::vector<std::pair<std::string, uint16_t>> &owners_weights, const std::string &object_id) const {
const std::string update_owners_encoder::function_signature = "23ab6adf"; //! updateOwners((address,uint256)[],string)
std::string update_owners_encoder::encode(const std::vector<std::pair<std::string, uint16_t>> &owners_weights, const std::string &object_id) {
std::string data = "0x" + function_signature;
data += base_encoder::encode_uint256(64);
data += base_encoder::encode_uint256((owners_weights.size() * 2 + 3) * 32);
@ -39,7 +42,8 @@ std::string update_owners_encoder::encode(const std::vector<std::pair<std::strin
}
//! withdrawal_encoder
std::string withdrawal_encoder::encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id) const {
const std::string withdrawal_encoder::function_signature = "e088747b"; //! withdraw(address,uint256,string)
std::string withdrawal_encoder::encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id) {
std::string data = "0x" + function_signature;
data += base_encoder::encode_address(to);
data += base_encoder::encode_uint256(amount);
@ -49,6 +53,51 @@ std::string withdrawal_encoder::encode(const std::string &to, boost::multiprecis
return data;
}
//! signature_encoder
signature_encoder::signature_encoder(const std::string &function_hash) :
function_signature{function_hash} {
}
std::string signature_encoder::get_function_signature_from_transaction(const std::string &transaction) {
const std::string tr = remove_0x(transaction);
if (tr.substr(0, 8) == update_owners_encoder::function_signature)
return update_owners_function_signature;
if (tr.substr(0, 8) == withdrawal_encoder::function_signature)
return withdrawal_function_signature;
return "";
}
std::string signature_encoder::encode(const std::vector<encoded_sign_transaction> &transactions) const {
std::string data = "0x" + function_signature;
data += base_encoder::encode_uint256(32);
data += base_encoder::encode_uint256(transactions.size());
size_t offset = (transactions.size()) * 32;
for (const auto &transaction : transactions) {
data += base_encoder::encode_uint256(offset);
const auto transaction_data = remove_0x(transaction.data);
offset += 5 * 32 + transaction_data.size() / 2;
if (transaction_data.size() / 2 % 32 != 0) {
offset += 32 - transaction_data.size() / 2 % 32;
}
}
for (const auto &transaction : transactions) {
data += base_encoder::encode_uint256(4 * 32);
data += base_encoder::encode_address(transaction.sign.v);
data += base_encoder::encode_address(transaction.sign.r);
data += base_encoder::encode_address(transaction.sign.s);
const auto transaction_data = remove_0x(transaction.data);
data += base_encoder::encode_uint256(transaction_data.size() / 2);
data += transaction_data;
if (transaction_data.size() % 64 != 0) {
data += std::string((64 - transaction_data.size() % 64), '0');
}
}
return data;
}
//! rlp_encoder
std::string rlp_encoder::encode(const std::string &s) {
return encode_rlp(hex2bytes(s));

View file

@ -22,7 +22,43 @@ const secp256k1_context *eth_context() {
return ctx;
}
//! transaction
bytes keccak_hash(const std::string &data) {
bytes hash;
hash.resize(32);
const auto transaction_string = boost::algorithm::unhex(remove_0x(data));
keccak_256((const unsigned char *)transaction_string.data(), transaction_string.size(), (unsigned char *)hash.data());
return hash;
}
signature sign_hash(const bytes &hash, const std::string &chain_id, const std::string &private_key) {
const bytes priv_key = parse_hex(private_key);
int recid = 0;
secp256k1_ecdsa_recoverable_signature sig;
FC_ASSERT(secp256k1_ecdsa_sign_recoverable(eth_context(), &sig, (const unsigned char *)hash.data(), (const unsigned char *)priv_key.data(), NULL, NULL));
fc::ecc::compact_signature result;
FC_ASSERT(secp256k1_ecdsa_recoverable_signature_serialize_compact(eth_context(), (unsigned char *)result.begin() + 1, &recid, &sig));
unsigned int v = recid + from_hex<unsigned int>(chain_id) * 2 + 35;
bytes r;
for (int i = 1; i < 33; i++)
r.emplace_back((char)result.at(i));
bytes s;
for (int i = 33; i < 65; i++)
s.emplace_back((char)result.at(i));
signature eth_sig;
eth_sig.v = to_hex(v);
eth_sig.r = fc::to_hex((char *)&r[0], r.size());
eth_sig.s = fc::to_hex((char *)&s[0], s.size());
return eth_sig;
}
//! base_transaction
base_transaction::base_transaction(const std::string &raw_tx) {
}
@ -70,12 +106,7 @@ raw_transaction::raw_transaction(const std::string &raw_tx) :
}
bytes raw_transaction::hash() const {
bytes hash;
hash.resize(32);
const auto transaction_string = boost::algorithm::unhex(remove_0x(serialize()));
keccak_256((const unsigned char *)transaction_string.data(), transaction_string.size(), (unsigned char *)hash.data());
return hash;
return keccak_hash(serialize());
}
signed_transaction raw_transaction::sign(const std::string &private_key) const {
@ -88,27 +119,10 @@ signed_transaction raw_transaction::sign(const std::string &private_key) const {
tr.value = value;
tr.data = data;
const bytes priv_key = parse_hex(private_key);
int recid = 0;
secp256k1_ecdsa_recoverable_signature sig;
FC_ASSERT(secp256k1_ecdsa_sign_recoverable(eth_context(), &sig, (const unsigned char *)hash().data(), (const unsigned char *)priv_key.data(), NULL, NULL));
fc::ecc::compact_signature result;
FC_ASSERT(secp256k1_ecdsa_recoverable_signature_serialize_compact(eth_context(), (unsigned char *)result.begin() + 1, &recid, &sig));
bytes r;
for (int i = 1; i < 33; i++)
r.emplace_back((char)result.at(i));
unsigned int v = recid + from_hex<unsigned int>(chain_id) * 2 + 35;
bytes s;
for (int i = 33; i < 65; i++)
s.emplace_back((char)result.at(i));
tr.r = fc::to_hex((char *)&r[0], r.size());
tr.v = to_hex(v);
tr.s = fc::to_hex((char *)&s[0], s.size());
const auto sig = sign_hash(hash(), chain_id, private_key);
tr.v = sig.v;
tr.r = sig.r;
tr.s = sig.s;
return tr;
}
@ -196,8 +210,8 @@ std::string signed_transaction::serialize() const {
rlp_encoder::encode(remove_0x(value)) +
rlp_encoder::encode(remove_0x(data)) +
rlp_encoder::encode(remove_0x(v)) +
rlp_encoder::encode(remove_0x(r)) +
rlp_encoder::encode(remove_0x(s));
rlp_encoder::encode(remove_leading_00(remove_0x(r))) +
rlp_encoder::encode(remove_leading_00(remove_0x(s)));
return add_0x(bytes2hex(rlp_encoder::encode_length(serialized.size(), 192) + serialized));
}
@ -220,9 +234,9 @@ void signed_transaction::deserialize(const std::string &raw_tx) {
boost::algorithm::to_lower(data);
v = add_0x(rlp_array.at(6));
boost::algorithm::to_lower(v);
r = add_0x(rlp_array.at(7));
r = add_0x(add_leading_00(rlp_array.at(7)));
boost::algorithm::to_lower(r);
s = add_0x(rlp_array.at(8));
s = add_0x(add_leading_00(rlp_array.at(8)));
boost::algorithm::to_lower(s);
}

View file

@ -1,5 +1,36 @@
#include <graphene/peerplays_sidechain/ethereum/types.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
namespace graphene { namespace peerplays_sidechain { namespace ethereum {
signature::signature(const std::string &sign) {
deserialize(sign);
}
std::string signature::serialize() const {
boost::property_tree::ptree pt;
pt.put("v", v);
pt.put("r", r);
pt.put("s", s);
std::stringstream ss;
boost::property_tree::json_parser::write_json(ss, pt);
return ss.str();
}
void signature::deserialize(const std::string &raw_tx) {
std::stringstream ss_tx(raw_tx);
boost::property_tree::ptree tx_json;
boost::property_tree::read_json(ss_tx, tx_json);
if (tx_json.count("v"))
v = tx_json.get<std::string>("v");
if (tx_json.count("r"))
r = tx_json.get<std::string>("r");
if (tx_json.count("s"))
s = tx_json.get<std::string>("s");
}
}}} // namespace graphene::peerplays_sidechain::ethereum

View file

@ -49,4 +49,24 @@ std::string remove_0x(const std::string &s) {
return s;
}
std::string add_leading_00(const std::string &s) {
std::string result = s;
while (result.size() < 64) {
result = "00" + result;
}
return result;
}
std::string remove_leading_00(const std::string &s) {
std::string result = s;
while (result.size() > 1 && result.substr(0, 2) == "00") {
result = result.substr(2);
}
return result;
}
}}} // namespace graphene::peerplays_sidechain::ethereum

View file

@ -4,8 +4,18 @@
#include <string>
#include <vector>
#include <graphene/peerplays_sidechain/ethereum/types.hpp>
namespace graphene { namespace peerplays_sidechain { namespace ethereum {
const std::string update_owners_function_signature = "9d608673"; //! updateOwners((bytes,(uint8,bytes32,bytes32))[])
const std::string withdrawal_function_signature = "daac6c81"; //! withdraw((bytes,(uint8,bytes32,bytes32))[])
struct encoded_sign_transaction {
std::string data;
signature sign;
};
class base_encoder {
public:
static std::string encode_uint256(boost::multiprecision::uint256_t value);
@ -15,16 +25,27 @@ public:
class update_owners_encoder {
public:
const std::string function_signature = "23ab6adf"; //! updateOwners((address,uint256)[],string)
static const std::string function_signature;
std::string encode(const std::vector<std::pair<std::string, uint16_t>> &owners_weights, const std::string &object_id) const;
static std::string encode(const std::vector<std::pair<std::string, uint16_t>> &owners_weights, const std::string &object_id);
};
class withdrawal_encoder {
public:
const std::string function_signature = "e088747b"; //! withdraw(address,uint256,string)
static const std::string function_signature;
std::string encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id) const;
static std::string encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id);
};
class signature_encoder {
public:
const std::string function_signature;
signature_encoder(const std::string &function_hash);
static std::string get_function_signature_from_transaction(const std::string &transaction);
std::string encode(const std::vector<encoded_sign_transaction> &transactions) const;
};
class rlp_encoder {
@ -39,35 +60,4 @@ private:
static void hex2bin(const char *src, char *target);
};
/*class ethereum_function_call_encoder {
public:
enum operation_t {
OPERATION_CALL,
OPERATION_DELEGATE_CALL
};
static constexpr const char *const default_prev_addr = "0000000000000000000000000000000000000001";
std::string encode_function_signature(const std::string &function_signature);
std::string encode_address(const std::string &addr);
std::string encode_uint256(const std::string &value);
std::string encode_uint8(uint8_t value);
std::string encode_bytes(const std::string &values);
};
class safe_transaction_encoder {
public:
static constexpr const char *const default_safe_tx_gas = "0";
static constexpr const char *const default_data_gas = "0";
static constexpr const char *const default_gas_price = "0";
static constexpr const char *const default_gas_token = "0000000000000000000000000000000000000000";
static constexpr const char *const default_refund_receiver = "0000000000000000000000000000000000000000";
std::string create_safe_address(const std::vector<std::string> &owner_addresses, uint32_t threshold);
std::string build_transaction(const std::string &safe_account_addr, const std::string &value, const std::string &data, uint8_t operation, const std::string &safeTxGas, const std::string &dataGas, const std::string &gasPrice, const std::string &gasToken, const std::string &refundReceiver);
private:
ethereum_function_call_encoder m_ethereum_function_call_encoder;
};*/
}}} // namespace graphene::peerplays_sidechain::ethereum

View file

@ -8,6 +8,9 @@
namespace graphene { namespace peerplays_sidechain { namespace ethereum {
bytes keccak_hash(const std::string &data);
signature sign_hash(const bytes &hash, const std::string &chain_id, const std::string &private_key);
class base_transaction {
public:
base_transaction() = default;
@ -75,89 +78,3 @@ public:
};
}}} // namespace graphene::peerplays_sidechain::ethereum
// Example 1
//{
// "blockHash": "0x64a6706ecaf5a97b7f3e047abb20ff223ce82c6994d80e68fdb1fdfb38d0209c",
// "blockNumber": "0xe5827c",
// "from": "0x8614c67e085f2334010f2a28e806c6f1cc176d12",
// "gas": "0x38822",
// "gasPrice": "0xce42cba69",
// "maxFeePerGas": "0xddb4d8d16",
// "maxPriorityFeePerGas": "0x3b9aca00",
// "hash": "0xeac92ea09fa8eb3ca2fb0d156cceb38ae69d4345869d41e8e49d5ecbcbb622dc",
// "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000062bb57cf00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000002514d9d7d7d8000000000000000000000000000000000000000000007dced93dd41fd3e1f9e80c200000000000000000000000000000000000000000000000000000000000000800000000000000000000000008614c67e085f2334010f2a28e806c6f1cc176d120000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000059c12ed5aaf25adbc6e15f9cc9bab2dde03121500000000000000000000000000000000000000000000000000000000",
// "nonce": "0x32",
// "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45",
// "transactionIndex": "0xb6",
// "value": "0x2514d9d7d7d8000",
// "type": "0x2",
// "accessList": [],
// "chainId": "0x1",
// "v": "0x1",
// "r": "0x2f8d6a9c737ed98792bafc903b8f1aa54adc731bd3cf9a8b25246a1c9095a28c",
// "s": "0x782c40e64b47a221a07612c822c08763f626e53c4b00b73f4c5ba86304c43f14"
//}
//
//"0xf9021332850ce42cba69830388229468b3465833fb72a70ecdf485e0e4c7bd8665fc458802514d9d7d7d8000b901a45ae401dc0000000000000000000000000000000000000000000000000000000062bb57cf00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000002514d9d7d7d8000000000000000000000000000000000000000000007dced93dd41fd3e1f9e80c200000000000000000000000000000000000000000000000000000000000000800000000000000000000000008614c67e085f2334010f2a28e806c6f1cc176d120000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000059c12ed5aaf25adbc6e15f9cc9bab2dde0312150000000000000000000000000000000000000000000000000000000001a02f8d6a9c737ed98792bafc903b8f1aa54adc731bd3cf9a8b25246a1c9095a28ca0782c40e64b47a221a07612c822c08763f626e53c4b00b73f4c5ba86304c43f14"
//
//{
// "nonce": 50,
// "gasPrice": {
// "_hex": "0x0ce42cba69"
// },
// "gasLimit": {
// "_hex": "0x038822"
// },
// "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45",
// "value": {
// "_hex": "0x02514d9d7d7d8000"
// },
// "data": "0x5ae401dc0000000000000000000000000000000000000000000000000000000062bb57cf00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000002514d9d7d7d8000000000000000000000000000000000000000000007dced93dd41fd3e1f9e80c200000000000000000000000000000000000000000000000000000000000000800000000000000000000000008614c67e085f2334010f2a28e806c6f1cc176d120000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000059c12ed5aaf25adbc6e15f9cc9bab2dde03121500000000000000000000000000000000000000000000000000000000",
// "v": 1,
// "r": "0x2f8d6a9c737ed98792bafc903b8f1aa54adc731bd3cf9a8b25246a1c9095a28c",
// "s": "0x782c40e64b47a221a07612c822c08763f626e53c4b00b73f4c5ba86304c43f14"
//}
// Example 2
//{
// "blockHash": "0xe2ae3afd86dc7343c7fb753441447a0a51bb19499325ad6e278256f0cd1b5894",
// "blockNumber": "0xe58271",
// "from": "0xb895ade6d337fbb8cb97f2ea7da43106c7f5cc26",
// "gas": "0x5208",
// "gasPrice": "0xe6f3b322e",
// "maxFeePerGas": "0x1322455fd3",
// "maxPriorityFeePerGas": "0x53724e00",
// "hash": "0xed29b56e52ad2d452e25b8ec70c37f59d935cd6d0f8fe8e83b256f3ffdfd3fce",
// "input": "0x",
// "nonce": "0x37",
// "to": "0x176386b6ffc469ac049f9ec1f6cc0efd1d09b373",
// "transactionIndex": "0x8a",
// "value": "0x4563918244f40000",
// "type": "0x2",
// "accessList": [],
// "chainId": "0x1",
// "v": "0x0",
// "r": "0xdcc588257770e08660cb809e71b293f556cd5f5323e832d96ee896ff8830ca4c",
// "s": "0x28c7ce6a539d9318688687097a2db29e15c32ba8c085275fdd3dddf047d4bd1a"
//}
//
//"0xf86c37850e6f3b322e82520894176386b6ffc469ac049f9ec1f6cc0efd1d09b373884563918244f400008000a0dcc588257770e08660cb809e71b293f556cd5f5323e832d96ee896ff8830ca4ca028c7ce6a539d9318688687097a2db29e15c32ba8c085275fdd3dddf047d4bd1a"
//
//{
// "nonce": 55,
// "gasPrice": {
// "_hex": "0x0e6f3b322e"
// },
// "gasLimit": {
// "_hex": "0x5208"
// },
// "to": "0x176386b6ffc469ac049f9ec1f6cc0efd1d09b373",
// "value": {
// "_hex": "0x4563918244f40000"
// },
// "data": "0x",
// "v": 0,
// "r": "0xdcc588257770e08660cb809e71b293f556cd5f5323e832d96ee896ff8830ca4c",
// "s": "0x28c7ce6a539d9318688687097a2db29e15c32ba8c085275fdd3dddf047d4bd1a"
//}

View file

@ -9,4 +9,17 @@ typedef uint64_t network_id_type;
using bytes = std::vector<char>;
class signature {
public:
std::string v;
std::string r;
std::string s;
signature() = default;
signature(const std::string &sign);
std::string serialize() const;
void deserialize(const std::string &sign);
};
}}} // namespace graphene::peerplays_sidechain::ethereum

View file

@ -14,12 +14,16 @@ std::string add_0x(const std::string &s);
std::string remove_0x(const std::string &s);
std::string add_leading_00(const std::string &s);
std::string remove_leading_00(const std::string &s);
template <typename T>
std::string to_hex(const T &val, bool add_front_zero = true) {
std::stringstream stream;
stream << std::hex << val;
std::string result(stream.str());
if(add_front_zero) {
if (add_front_zero) {
if (result.size() % 2)
result = "0" + result;
}

View file

@ -20,10 +20,11 @@ public:
sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options);
virtual ~sidechain_net_handler();
sidechain_type get_sidechain();
std::vector<std::string> get_sidechain_deposit_addresses();
std::vector<std::string> get_sidechain_withdraw_addresses();
std::string get_private_key(std::string public_key);
sidechain_type get_sidechain() const;
std::vector<std::string> get_sidechain_deposit_addresses() const;
std::vector<std::string> get_sidechain_withdraw_addresses() const;
std::vector<sidechain_transaction_object> get_sidechain_transaction_objects(sidechain_transaction_status status) const;
std::string get_private_key(std::string public_key) const;
bool proposal_exists(int32_t operation_tag, const object_id_type &object_id, boost::optional<chain::operation &> proposal_op = boost::none);
bool signer_expected(const sidechain_transaction_object &sto, son_id_type signer);

View file

@ -34,6 +34,7 @@ public:
std::string eth_send_transaction(const std::string &params);
std::string eth_send_raw_transaction(const std::string &params);
std::string eth_get_transaction_receipt(const std::string &params);
std::string eth_get_transaction_by_hash(const std::string &params);
};
class sidechain_net_handler_ethereum : public sidechain_net_handler {

View file

@ -398,14 +398,14 @@ bool peerplays_sidechain_plugin_impl::is_son_down_op_valid(const chain::operatio
const chain::global_property_object &gpo = d.get_global_properties();
const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties();
const auto &idx = d.get_index_type<chain::son_index>().indices().get<by_id>();
son_report_down_operation down_op = op.get<son_report_down_operation>();
auto son_obj = idx.find(down_op.son_id);
const son_report_down_operation down_op = op.get<son_report_down_operation>();
const auto son_obj = idx.find(down_op.son_id);
if (son_obj == idx.end()) {
return false;
}
auto stats = son_obj->statistics(d);
fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval;
int64_t down_threshold = gpo.parameters.son_down_time();
const auto stats = son_obj->statistics(d);
const fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval;
const int64_t down_threshold = gpo.parameters.son_down_time();
bool status_son_down_op_valid = true;
for (const auto &status : son_obj->statuses) {
@ -414,9 +414,11 @@ bool peerplays_sidechain_plugin_impl::is_son_down_op_valid(const chain::operatio
}
if (status_son_down_op_valid) {
for (const auto &active_sidechain_type : active_sidechain_types) {
fc::time_point_sec last_active_ts = ((stats.last_active_timestamp.at(active_sidechain_type) > last_maintenance_time) ? stats.last_active_timestamp.at(active_sidechain_type) : last_maintenance_time);
if (((fc::time_point::now() - last_active_ts) <= fc::seconds(down_threshold))) {
status_son_down_op_valid = false;
if (stats.last_active_timestamp.contains(active_sidechain_type)) {
const fc::time_point_sec last_active_ts = ((stats.last_active_timestamp.at(active_sidechain_type) > last_maintenance_time) ? stats.last_active_timestamp.at(active_sidechain_type) : last_maintenance_time);
if (((fc::time_point::now() - last_active_ts) <= fc::seconds(down_threshold))) {
status_son_down_op_valid = false;
}
}
}
}
@ -705,7 +707,7 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals(sidechain_type s
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = get_current_son_object(sidechain).son_account;
proposal_op.proposed_ops.emplace_back(op_wrapper(son_down_op));
uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
const uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
proposal_op.expiration_time = time_point_sec(d.head_block_time().sec_since_epoch() + lifetime);
return proposal_op;
};
@ -714,19 +716,27 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals(sidechain_type s
const chain::global_property_object &gpo = d.get_global_properties();
const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties();
const auto &idx = d.get_index_type<chain::son_index>().indices().get<by_id>();
std::set<son_id_type> sons_being_reported_down = d.get_sons_being_reported_down();
chain::son_id_type my_son_id = get_current_son_id(sidechain);
const std::set<son_id_type> sons_being_reported_down = d.get_sons_being_reported_down();
const chain::son_id_type my_son_id = get_current_son_id(sidechain);
//! Fixme - check this part of the code
for (auto son_inf : gpo.active_sons.at(sidechain)) {
if (my_son_id == son_inf.son_id || (sons_being_reported_down.find(son_inf.son_id) != sons_being_reported_down.end())) {
continue;
}
auto son_obj = idx.find(son_inf.son_id);
auto stats = son_obj->statistics(d);
fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval;
fc::time_point_sec last_active_ts = ((stats.last_active_timestamp.at(sidechain) > last_maintenance_time) ? stats.last_active_timestamp.at(sidechain) : last_maintenance_time);
int64_t down_threshold = gpo.parameters.son_down_time();
const auto son_obj = idx.find(son_inf.son_id);
const auto stats = son_obj->statistics(d);
const fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval;
const fc::time_point_sec last_active_ts = [&stats, &sidechain, &last_maintenance_time] {
fc::time_point_sec last_active_ts;
if (stats.last_active_timestamp.contains(sidechain)) {
last_active_ts = (stats.last_active_timestamp.at(sidechain) > last_maintenance_time) ? stats.last_active_timestamp.at(sidechain) : last_maintenance_time;
} else
last_active_ts = last_maintenance_time;
return last_active_ts;
}();
const int64_t down_threshold = gpo.parameters.son_down_time();
bool status_son_down_valid = true;
for (const auto &status : son_obj->statuses) {
@ -756,7 +766,7 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals(sidechain_type s
}
void peerplays_sidechain_plugin_impl::create_son_deregister_proposals(sidechain_type sidechain) {
const std::lock_guard<std::mutex> lck{access_son_down_prop_mutex};
const std::lock_guard<std::mutex> lck{access_son_deregister_prop_mutex};
chain::database &d = plugin.database();
std::set<son_id_type> sons_to_be_dereg = d.get_sons_to_be_deregistered();
chain::son_id_type my_son_id = get_current_son_id(sidechain);

View file

@ -21,11 +21,11 @@ sidechain_net_handler::sidechain_net_handler(peerplays_sidechain_plugin &_plugin
sidechain_net_handler::~sidechain_net_handler() {
}
sidechain_type sidechain_net_handler::get_sidechain() {
sidechain_type sidechain_net_handler::get_sidechain() const {
return sidechain;
}
std::vector<std::string> sidechain_net_handler::get_sidechain_deposit_addresses() {
std::vector<std::string> sidechain_net_handler::get_sidechain_deposit_addresses() const {
std::vector<std::string> result;
const auto &sidechain_addresses_idx = database.get_index_type<sidechain_address_index>();
@ -38,7 +38,7 @@ std::vector<std::string> sidechain_net_handler::get_sidechain_deposit_addresses(
return result;
}
std::vector<std::string> sidechain_net_handler::get_sidechain_withdraw_addresses() {
std::vector<std::string> sidechain_net_handler::get_sidechain_withdraw_addresses() const {
std::vector<std::string> result;
const auto &sidechain_addresses_idx = database.get_index_type<sidechain_address_index>();
@ -51,7 +51,20 @@ std::vector<std::string> sidechain_net_handler::get_sidechain_withdraw_addresses
return result;
}
std::string sidechain_net_handler::get_private_key(std::string public_key) {
std::vector<sidechain_transaction_object> sidechain_net_handler::get_sidechain_transaction_objects(sidechain_transaction_status status) const {
std::vector<sidechain_transaction_object> result;
const auto &idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_sidechain_and_status>();
const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, status));
std::for_each(idx_range.first, idx_range.second,
[&result](const sidechain_transaction_object &sto) {
result.push_back(sto);
});
return result;
}
std::string sidechain_net_handler::get_private_key(std::string public_key) const {
auto private_key_itr = private_keys.find(public_key);
if (private_key_itr != private_keys.end()) {
return private_key_itr->second;
@ -473,10 +486,9 @@ void sidechain_net_handler::process_withdrawals() {
}
void sidechain_net_handler::process_sidechain_transactions() {
const auto &idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_sidechain_and_status>();
const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::valid));
const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::valid);
std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) {
std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) {
if ((sto.id == object_id_type(0, 0, 0)) || !signer_expected(sto, plugin.get_current_son_id(sidechain))) {
return;
}
@ -520,10 +532,9 @@ void sidechain_net_handler::process_sidechain_transactions() {
}
void sidechain_net_handler::send_sidechain_transactions() {
const auto &idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_sidechain_and_status>();
const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::complete));
const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::complete);
std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) {
std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) {
if (sto.id == object_id_type(0, 0, 0)) {
return;
}
@ -555,10 +566,9 @@ void sidechain_net_handler::send_sidechain_transactions() {
}
void sidechain_net_handler::settle_sidechain_transactions() {
const auto &idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_sidechain_and_status>();
const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::sent));
const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::sent);
std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) {
std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) {
if (sto.id == object_id_type(0, 0, 0)) {
return;
}

View file

@ -76,7 +76,7 @@ std::string ethereum_rpc_client::get_network_id() {
}
std::string ethereum_rpc_client::get_nonce(const std::string &address) {
const std::string reply_str = eth_get_transaction_count("[\"" + address + "\", \"latest\"]");
const std::string reply_str = eth_get_transaction_count("[\"" + address + "\", \"pending\"]");
const auto nonce_string = retrieve_value_from_reply(reply_str, "");
if (!nonce_string.empty()) {
const auto nonce_val = ethereum::from_hex<boost::multiprecision::uint256_t>(nonce_string);
@ -121,6 +121,10 @@ std::string ethereum_rpc_client::eth_get_transaction_receipt(const std::string &
return send_post_request("eth_getTransactionReceipt", "[\"" + params + "\"]", debug_rpc_calls);
}
std::string ethereum_rpc_client::eth_get_transaction_by_hash(const std::string &params) {
return send_post_request("eth_getTransactionByHash", "[\"" + params + "\"]", debug_rpc_calls);
}
sidechain_net_handler_ethereum::sidechain_net_handler_ethereum(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) :
sidechain_net_handler(_plugin, options) {
sidechain = sidechain_type::ethereum;
@ -213,13 +217,13 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
case chain::operation::tag<chain::son_wallet_update_operation>::value: {
bool address_ok = false;
bool transaction_ok = false;
son_wallet_id_type swo_id = op_obj_idx_0.get<son_wallet_update_operation>().son_wallet_id;
const son_wallet_id_type swo_id = op_obj_idx_0.get<son_wallet_update_operation>().son_wallet_id;
const auto &idx = database.get_index_type<son_wallet_index>().indices().get<by_id>();
const auto swo = idx.find(swo_id);
if (swo != idx.end()) {
auto active_sons = gpo.active_sons.at(sidechain);
vector<son_info> wallet_sons = swo->sons.at(sidechain);
const auto active_sons = gpo.active_sons.at(sidechain);
const vector<son_info> wallet_sons = swo->sons.at(sidechain);
bool son_sets_equal = (active_sons.size() == wallet_sons.size());
@ -234,8 +238,8 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
}
if (po.proposed_transaction.operations.size() >= 2) {
object_id_type object_id = op_obj_idx_1.get<sidechain_transaction_create_operation>().object_id;
std::string op_tx_str = op_obj_idx_1.get<sidechain_transaction_create_operation>().transaction;
const object_id_type object_id = op_obj_idx_1.get<sidechain_transaction_create_operation>().object_id;
const std::string op_tx_str = op_obj_idx_1.get<sidechain_transaction_create_operation>().transaction;
const auto &st_idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_object_id>();
const auto st = st_idx.find(object_id);
@ -265,64 +269,45 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
case chain::operation::tag<chain::son_wallet_deposit_process_operation>::value: {
bool process_ok = false;
son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get<son_wallet_deposit_process_operation>().son_wallet_deposit_id;
const son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get<son_wallet_deposit_process_operation>().son_wallet_deposit_id;
const auto &idx = database.get_index_type<son_wallet_deposit_index>().indices().get<by_id>();
const auto swdo = idx.find(swdo_id);
if (swdo != idx.end()) {
//std::string swdo_txid = swdo->sidechain_transaction_id;
//std::string swdo_sidechain_from = swdo->sidechain_from;
//std::string swdo_sidechain_currency = swdo->sidechain_currency;
//uint64_t swdo_sidechain_amount = swdo->sidechain_amount.value;
//uint64_t swdo_op_idx = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-")));
//
//std::string tx_str = rpc_client->account_history_api_get_transaction(swdo_txid);
//if (tx_str != "") {
//
// std::stringstream ss_tx(tx_str);
// boost::property_tree::ptree tx;
// boost::property_tree::read_json(ss_tx, tx);
//
// uint64_t op_idx = -1;
// for (const auto &ops : tx.get_child("result.operations")) {
// const auto &op = ops.second;
// op_idx = op_idx + 1;
// if (op_idx == swdo_op_idx) {
// std::string operation_type = op.get<std::string>("type");
//
// if (operation_type == "transfer_operation") {
// const auto &op_value = op.get_child("value");
//
// std::string sidechain_from = op_value.get<std::string>("from");
//
// const auto &amount_child = op_value.get_child("amount");
//
// uint64_t amount = amount_child.get<uint64_t>("amount");
// std::string nai = amount_child.get<std::string>("nai");
// std::string sidechain_currency = "";
// if ((nai == "@@000000013" /*?? HBD*/) || (nai == "@@000000013" /*TBD*/)) {
// sidechain_currency = "HBD";
// }
// if ((nai == "@@000000021") /*?? HIVE*/ || (nai == "@@000000021" /*TESTS*/)) {
// sidechain_currency = "HIVE";
// }
//
// std::string memo = op_value.get<std::string>("memo");
// boost::trim(memo);
// if (!memo.empty()) {
// sidechain_from = memo;
// }
//
// process_ok = (swdo_sidechain_from == sidechain_from) &&
// (swdo_sidechain_currency == sidechain_currency) &&
// (swdo_sidechain_amount == amount);
// }
// }
// }
//}
}
const std::string swdo_txid = swdo->sidechain_transaction_id;
const std::string swdo_sidechain_from = swdo->sidechain_from;
const std::string swdo_sidechain_currency = swdo->sidechain_currency;
const uint64_t swdo_sidechain_amount = swdo->sidechain_amount.value;
process_ok = true;
const std::string tx_str = rpc_client->eth_get_transaction_by_hash(swdo_txid);
if (tx_str != "") {
std::stringstream ss_tx(tx_str);
boost::property_tree::ptree tx;
boost::property_tree::read_json(ss_tx, tx);
if (tx.get<std::string>("result") != "null") {
const std::string sidechain_from = tx.get<std::string>("result.from");
const std::string sidechain_to = tx.get<std::string>("result.to");
const std::string value_s = tx.get<std::string>("result.value");
boost::multiprecision::uint256_t amount(value_s);
amount = amount / 100000;
amount = amount / 100000;
const fc::safe<uint64_t> sidechain_amount = amount;
std::string cmp_sidechain_to = sidechain_to;
std::transform(cmp_sidechain_to.begin(), cmp_sidechain_to.end(), cmp_sidechain_to.begin(), ::toupper);
std::string cmp_wallet_contract_address = wallet_contract_address;
std::transform(cmp_wallet_contract_address.begin(), cmp_wallet_contract_address.end(), cmp_wallet_contract_address.begin(), ::toupper);
process_ok = (swdo_sidechain_from == sidechain_from) &&
(cmp_sidechain_to == cmp_wallet_contract_address) &&
(swdo_sidechain_currency == "ETH") &&
(swdo_sidechain_amount == sidechain_amount.value);
}
}
}
should_approve = process_ok;
break;
@ -331,23 +316,23 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
case chain::operation::tag<chain::son_wallet_withdraw_process_operation>::value: {
bool process_ok = false;
bool transaction_ok = false;
son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get<son_wallet_withdraw_process_operation>().son_wallet_withdraw_id;
const son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get<son_wallet_withdraw_process_operation>().son_wallet_withdraw_id;
const auto &idx = database.get_index_type<son_wallet_withdraw_index>().indices().get<by_id>();
const auto swwo = idx.find(swwo_id);
if (swwo != idx.end()) {
uint32_t swwo_block_num = swwo->block_num;
std::string swwo_peerplays_transaction_id = swwo->peerplays_transaction_id;
uint32_t swwo_op_idx = std::stoll(swwo->peerplays_uid.substr(swwo->peerplays_uid.find_last_of("-") + 1));
const uint32_t swwo_block_num = swwo->block_num;
const std::string swwo_peerplays_transaction_id = swwo->peerplays_transaction_id;
const uint32_t swwo_op_idx = std::stoll(swwo->peerplays_uid.substr(swwo->peerplays_uid.find_last_of("-") + 1));
const auto &block = database.fetch_block_by_number(swwo_block_num);
for (const auto &tx : block->transactions) {
if (tx.id().str() == swwo_peerplays_transaction_id) {
operation op = tx.operations[swwo_op_idx];
transfer_operation t_op = op.get<transfer_operation>();
const operation op = tx.operations[swwo_op_idx];
const transfer_operation t_op = op.get<transfer_operation>();
price asset_price = database.get<asset_object>(t_op.amount.asset_id).options.core_exchange_rate;
asset peerplays_asset = asset(t_op.amount.amount * asset_price.base.amount / asset_price.quote.amount);
const price asset_price = database.get<asset_object>(t_op.amount.asset_id).options.core_exchange_rate;
const asset peerplays_asset = asset(t_op.amount.amount * asset_price.base.amount / asset_price.quote.amount);
process_ok = (t_op.to == gpo.parameters.son_account()) &&
(swwo->peerplays_from == t_op.from) &&
@ -356,8 +341,8 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
}
}
object_id_type object_id = op_obj_idx_1.get<sidechain_transaction_create_operation>().object_id;
std::string op_tx_str = op_obj_idx_1.get<sidechain_transaction_create_operation>().transaction;
const object_id_type object_id = op_obj_idx_1.get<sidechain_transaction_create_operation>().object_id;
const std::string op_tx_str = op_obj_idx_1.get<sidechain_transaction_create_operation>().transaction;
const auto &st_idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_object_id>();
const auto st = st_idx.find(object_id);
@ -384,9 +369,9 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po)
case chain::operation::tag<chain::sidechain_transaction_sign_operation>::value: {
should_approve = true;
son_id_type signer = op_obj_idx_0.get<sidechain_transaction_sign_operation>().signer;
std::string signature = op_obj_idx_0.get<sidechain_transaction_sign_operation>().signature;
sidechain_transaction_id_type sidechain_transaction_id = op_obj_idx_0.get<sidechain_transaction_sign_operation>().sidechain_transaction_id;
const son_id_type signer = op_obj_idx_0.get<sidechain_transaction_sign_operation>().signer;
const std::string signature = op_obj_idx_0.get<sidechain_transaction_sign_operation>().signature;
const sidechain_transaction_id_type sidechain_transaction_id = op_obj_idx_0.get<sidechain_transaction_sign_operation>().sidechain_transaction_id;
const auto &st_idx = database.get_index_type<sidechain_transaction_index>().indices().get<by_id>();
const auto sto = st_idx.find(sidechain_transaction_id);
if (sto == st_idx.end()) {
@ -497,6 +482,11 @@ void sidechain_net_handler_ethereum::process_sidechain_addresses() {
}
bool sidechain_net_handler_ethereum::process_deposit(const son_wallet_deposit_object &swdo) {
if (proposal_exists(chain::operation::tag<chain::son_wallet_deposit_process_operation>::value, swdo.id)) {
return false;
}
const chain::global_property_object &gpo = database.get_global_properties();
price asset_price = database.get<asset_object>(database.get_global_properties().parameters.eth_asset()).options.core_exchange_rate;
@ -585,33 +575,65 @@ std::string sidechain_net_handler_ethereum::process_sidechain_transaction(const
std::string sidechain_net_handler_ethereum::send_sidechain_transaction(const sidechain_transaction_object &sto) {
boost::property_tree::ptree pt;
boost::property_tree::ptree pt_array;
std::vector<ethereum::encoded_sign_transaction> transactions;
for (const auto &signature : sto.signatures) {
const auto &transaction = signature.second;
//! Check if we have this signed transaction, if not, don't send it
if (transaction.empty())
if (signature.second.empty())
continue;
ethereum::encoded_sign_transaction transaction{sto.transaction, ethereum::signature{signature.second}};
transactions.emplace_back(transaction);
}
const auto &current_son = plugin.get_current_son_object(sidechain);
FC_ASSERT(current_son.sidechain_public_keys.contains(sidechain), "No public keys for current son: ${account_id}", ("account_id", current_son.son_account));
const auto &public_key = current_son.sidechain_public_keys.at(sidechain);
const auto function_signature = ethereum::signature_encoder::get_function_signature_from_transaction(sto.transaction);
if (function_signature.empty()) {
elog("Function signature is empty for transaction id ${id}, transaction ${transaction}", ("id", sto.id)("transaction", sto.transaction));
return std::string{}; //! Return empty string, as we have error in sending
}
const ethereum::signature_encoder encoder{function_signature};
#ifdef SEND_RAW_TRANSACTION
const std::string sidechain_transaction = rpc_client->eth_send_raw_transaction(transaction);
ethereum::raw_transaction raw_tr;
raw_tr.nonce = rpc_client->get_nonce(ethereum::add_0x(public_key));
raw_tr.gas_price = rpc_client->get_gas_price();
raw_tr.gas_limit = rpc_client->get_gas_limit();
raw_tr.to = wallet_contract_address;
raw_tr.value = "";
raw_tr.data = encoder.encode(transactions);
raw_tr.chain_id = ethereum::add_0x(ethereum::to_hex(chain_id));
const auto sign_tr = raw_tr.sign(get_private_key(public_key));
const std::string sidechain_transaction = rpc_client->eth_send_raw_transaction(sign_tr.serialize());
#else
const std::string sidechain_transaction = rpc_client->eth_send_transaction(transaction);
ethereum::transaction raw_tr;
raw_tr.data = encoder.encode(transactions);
raw_tr.to = wallet_contract_address;
raw_tr.from = ethereum::add_0x(public_key);
const auto sign_tr = raw_tr.sign(get_private_key(public_key));
const std::string sidechain_transaction = rpc_client->eth_send_transaction(sign_tr.serialize());
#endif
std::stringstream ss_tx(sidechain_transaction);
boost::property_tree::ptree tx_json;
boost::property_tree::read_json(ss_tx, tx_json);
if (tx_json.count("result") && !tx_json.count("error")) {
boost::property_tree::ptree node;
node.put("transaction", transaction);
node.put("transaction_receipt", tx_json.get<std::string>("result"));
pt_array.push_back(std::make_pair("", node));
} else {
//! Fixme
//! How should we proceed with error in eth_send_transaction
elog("Error in eth_send_transaction for transaction ${id}, transaction ${transaction}", ("id", sto.id)("transaction", transaction));
return std::string{}; //! Return empty string, as we have error in sending
}
std::stringstream ss_tx(sidechain_transaction);
boost::property_tree::ptree tx_json;
boost::property_tree::read_json(ss_tx, tx_json);
if (tx_json.count("result") && !tx_json.count("error")) {
boost::property_tree::ptree node;
node.put("transaction", sto.transaction);
node.put("sidechain_transaction", sidechain_transaction);
node.put("transaction_receipt", tx_json.get<std::string>("result"));
pt_array.push_back(std::make_pair("", node));
} else {
//! Fixme
//! How should we proceed with error in eth_send_transaction
elog("Error in eth send transaction for transaction id ${id}, transaction ${transaction}, sidechain_transaction ${sidechain_transaction}", ("id", sto.id)("transaction", sto.transaction)("sidechain_transaction", sidechain_transaction));
return std::string{}; //! Return empty string, as we have error in sending
}
pt.add_child("result_array", pt_array);
@ -654,8 +676,18 @@ bool sidechain_net_handler_ethereum::settle_sidechain_transaction(const sidechai
if (count != json.get_child("result_array").size()) {
wlog("Not all receipts received for transaction ${id}", ("id", sto.id));
return false;
} else
} else {
if (sto.object_id.is<son_wallet_id_type>()) {
settle_amount = asset(0, database.get_global_properties().parameters.eth_asset());
}
if (sto.object_id.is<son_wallet_withdraw_id_type>()) {
auto swwo = database.get<son_wallet_withdraw_object>(sto.object_id);
settle_amount = asset(swwo.withdraw_amount, database.get_global_properties().parameters.eth_asset());
}
return true;
}
return false;
}
@ -688,8 +720,7 @@ optional<asset> sidechain_net_handler_ethereum::estimate_withdrawal_transaction_
}
const auto &public_key = son->sidechain_public_keys.at(sidechain);
const ethereum::withdrawal_encoder encoder;
const auto data = encoder.encode(public_key, 1 * 10000000000, son_wallet_withdraw_id_type{0}.operator object_id_type().operator std::string());
const auto data = ethereum::withdrawal_encoder::encode(public_key, 1 * 10000000000, son_wallet_withdraw_id_type{0}.operator object_id_type().operator std::string());
const std::string params = "[{\"from\":\"" + ethereum::add_0x(public_key) + "\", \"to\":\"" + wallet_contract_address + "\", \"data\":\"" + data + "\"}]";
const auto estimate_gas = ethereum::from_hex<int64_t>(rpc_client->get_estimate_gas(params));
@ -705,8 +736,7 @@ std::string sidechain_net_handler_ethereum::create_primary_wallet_transaction(co
owners_weights.emplace_back(std::make_pair(pub_key_str, son.weight));
}
const ethereum::update_owners_encoder encoder;
return encoder.encode(owners_weights, object_id);
return ethereum::update_owners_encoder::encode(owners_weights, object_id);
}
std::string sidechain_net_handler_ethereum::create_deposit_transaction(const son_wallet_deposit_object &swdo) {
@ -714,8 +744,7 @@ std::string sidechain_net_handler_ethereum::create_deposit_transaction(const son
}
std::string sidechain_net_handler_ethereum::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) {
const ethereum::withdrawal_encoder encoder;
return encoder.encode(swwo.withdraw_address.substr(2), swwo.withdraw_amount.value * 10000000000, swwo.id.operator std::string());
return ethereum::withdrawal_encoder::encode(swwo.withdraw_address.substr(2), swwo.withdraw_amount.value * 10000000000, swwo.id.operator std::string());
}
std::string sidechain_net_handler_ethereum::sign_transaction(const sidechain_transaction_object &sto) {
@ -724,25 +753,11 @@ std::string sidechain_net_handler_ethereum::sign_transaction(const sidechain_tra
const auto &public_key = current_son.sidechain_public_keys.at(sidechain);
#ifdef SEND_RAW_TRANSACTION
ethereum::raw_transaction raw_tr;
raw_tr.nonce = rpc_client->get_nonce(ethereum::add_0x(public_key));
raw_tr.gas_price = rpc_client->get_gas_price();
raw_tr.gas_limit = rpc_client->get_gas_limit();
raw_tr.to = wallet_contract_address;
raw_tr.value = "";
raw_tr.data = sto.transaction;
raw_tr.chain_id = ethereum::add_0x(ethereum::to_hex(chain_id));
//! We need to change v value according to chain_id
auto signature = ethereum::sign_hash(ethereum::keccak_hash(sto.transaction), ethereum::add_0x(ethereum::to_hex(chain_id)), get_private_key(public_key));
signature.v = ethereum::to_hex(ethereum::from_hex<unsigned int>(signature.v) - 2 * chain_id - 35 + 27);
const auto sign_tr = raw_tr.sign(get_private_key(public_key));
return sign_tr.serialize();
#else
ethereum::transaction sign_transaction;
sign_transaction.data = sto.transaction;
sign_transaction.to = wallet_contract_address;
sign_transaction.from = "0x" + public_key;
return sign_transaction.sign(get_private_key(public_key)).serialize();
#endif
return signature.serialize();
}
void sidechain_net_handler_ethereum::schedule_ethereum_listener() {

View file

@ -256,13 +256,14 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) {
const std::string memo_key = rpc_client->get_account_memo_key(wallet_account_name);
hive::authority active;
active.weight_threshold = total_weight * 2 / 3 + 1;
active.account_auths = account_auths;
hive::authority a;
a.weight_threshold = total_weight * 2 / 3 + 1;
a.account_auths = account_auths;
hive::account_update_operation auo;
auo.account = wallet_account_name;
auo.active = active;
auo.owner = a;
auo.active = a;
auo.memo_key = op_trx.operations[0].get<hive::account_update_operation>().memo_key;
hive::signed_transaction htrx;
@ -505,13 +506,14 @@ void sidechain_net_handler_hive::process_primary_wallet() {
return;
}
hive::authority active;
active.weight_threshold = total_weight * 2 / 3 + 1;
active.account_auths = account_auths;
hive::authority a;
a.weight_threshold = total_weight * 2 / 3 + 1;
a.account_auths = account_auths;
hive::account_update_operation auo;
auo.account = wallet_account_name;
auo.active = active;
auo.owner = a;
auo.active = a;
auo.memo_key = hive::public_key_type(memo_key);
const std::string block_id_str = rpc_client->get_head_block_id();
@ -546,12 +548,27 @@ void sidechain_net_handler_hive::process_primary_wallet() {
proposal_op.proposed_ops.emplace_back(swu_op);
const auto signers = [this, &prev_sw, &active_sw, &swi] {
std::vector<son_info> signers;
//! Check if we don't have any previous set of active SONs use the current one
if (prev_sw != swi.rend()) {
if (!prev_sw->sons.at(sidechain).empty())
signers = prev_sw->sons.at(sidechain);
else
signers = active_sw->sons.at(sidechain);
} else {
signers = active_sw->sons.at(sidechain);
}
return signers;
}();
sidechain_transaction_create_operation stc_op;
stc_op.payer = gpo.parameters.son_account();
stc_op.object_id = active_sw->id;
stc_op.sidechain = sidechain;
stc_op.transaction = tx_str;
stc_op.signers = gpo.active_sons.at(sidechain);
stc_op.signers = signers;
proposal_op.proposed_ops.emplace_back(stc_op);

View file

@ -191,17 +191,6 @@ struct worker_vote_delta
flat_set<worker_id_type> vote_abstain;
};
struct signed_block_with_info : public signed_block
{
signed_block_with_info();
signed_block_with_info( const signed_block& block );
signed_block_with_info( const signed_block_with_info& block ) = default;
block_id_type block_id;
public_key_type signing_key;
vector< transaction_id_type > transaction_ids;
};
struct vesting_balance_object_with_info : public vesting_balance_object
{
vesting_balance_object_with_info();
@ -276,7 +265,12 @@ class wallet_api
* @param num height of the block to retrieve
* @returns info about the block, or null if not found
*/
optional<signed_block_with_info> get_block( uint32_t num );
optional<signed_block> get_block( uint32_t num );
/** Returns info about a specified block, with some extra info.
* @param num height of the block to retrieve
* @returns info about the block, or null if not found
*/
optional<signed_block_with_info> get_block2( uint32_t num );
/** Get signed blocks
* @param block_num_from The lowest block number
* @param block_num_to The highest block number
@ -980,13 +974,13 @@ class wallet_api
*
* @return true if the label was set, otherwise false
*/
bool set_key_label( public_key_type, string label );
bool set_key_label( public_key_type key, string label );
/** Get label of a public key.
* @param key a public key
* @return the label if already set by \c set_key_label(), or an empty string if not set
*/
string get_key_label( public_key_type )const;
string get_key_label( public_key_type key )const;
/* Get the public key associated with a given label.
* @param label a label
@ -2571,9 +2565,29 @@ class wallet_api
/**
* @brief Returns all tokens
* @param limit the maximum number of NFT objects to return (max: 100)
* @param lower_id ID of the first NFT object to include in the list.
* @return Returns vector of NFT objects, empty vector if none
*/
vector<nft_object> nft_get_all_tokens() const;
vector<nft_object> nft_get_all_tokens(uint32_t limit, optional<nft_id_type> lower_id) const;
/**
* @brief Returns all tokens owned by owner
* @param owner NFT owner account ID
* @param limit the maximum number of NFT objects to return (max: 100)
* @param lower_id ID of the first NFT object to include in the list.
* @return Returns vector of NFT objects, empty vector if none
*/
vector<nft_object> nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional<nft_id_type> lower_id) const;
/**
* @brief Returns all NFT metadata objects owned by owner
* @param owner NFT owner account ID
* @param limit the maximum number of NFT metadata objects to return (max: 100)
* @param lower_id ID of the first NFT metadata object to include in the list.
* @return Returns vector of NFT metadata objects, empty vector if none
*/
vector<nft_metadata_object> nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional<nft_metadata_id_type> lower_id) const;
signed_transaction nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast );
signed_transaction create_offer(set<nft_id_type> item_ids,
@ -2742,9 +2756,6 @@ FC_REFLECT( graphene::wallet::worker_vote_delta,
(vote_abstain)
)
FC_REFLECT_DERIVED( graphene::wallet::signed_block_with_info, (graphene::chain::signed_block),
(block_id)(signing_key)(transaction_ids) )
FC_REFLECT_DERIVED( graphene::wallet::vesting_balance_object_with_info, (graphene::chain::vesting_balance_object),
(allowed_withdraw)(allowed_withdraw_time) )
@ -2862,6 +2873,7 @@ FC_API( graphene::wallet::wallet_api,
(get_account)
(get_account_id)
(get_block)
(get_block2)
(get_blocks)
(get_account_count)
(get_account_history)
@ -2943,6 +2955,8 @@ FC_API( graphene::wallet::wallet_api,
(nft_get_approved)
(nft_is_approved_for_all)
(nft_get_all_tokens)
(nft_get_tokens_by_owner)
(nft_get_metadata_by_owner)
(nft_lottery_buy_ticket)
(create_offer)
(create_bid)

View file

@ -3349,7 +3349,7 @@ public:
if(transaction_fee) {
if (*transaction_fee >= xfer_op.amount) {
FC_THROW("Transaction fee: ${sidechain_fee}, would be grater than transfer amount ${amount}",
FC_THROW("Transaction fee: ${sidechain_fee}, is greater than or equal to the transferred amount ${amount}",
("sidechain_fee", get_asset(transaction_fee->asset_id).amount_to_pretty_string(transaction_fee->amount))("amount", get_asset(xfer_op.amount.asset_id).amount_to_pretty_string(xfer_op.amount.amount)));
}
}
@ -4549,11 +4549,16 @@ bool wallet_api::copy_wallet_file(string destination_filename)
return my->copy_wallet_file(destination_filename);
}
optional<signed_block_with_info> wallet_api::get_block(uint32_t num)
optional<signed_block> wallet_api::get_block(uint32_t num)
{
return my->_remote_db->get_block(num);
}
optional<signed_block_with_info> wallet_api::get_block2(uint32_t num)
{
return my->_remote_db->get_block2(num);
}
vector<optional<signed_block>> wallet_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to) const
{
return my->_remote_db->get_blocks(block_num_from, block_num_to);
@ -7027,9 +7032,28 @@ bool wallet_api::nft_is_approved_for_all(string owner_account_id_or_name, string
return my->_remote_db->nft_is_approved_for_all(owner_account.id, operator_account.id);
}
vector<nft_object> wallet_api::nft_get_all_tokens() const
vector<nft_object> wallet_api::nft_get_all_tokens(uint32_t limit, optional<nft_id_type> lower_id) const
{
return my->_remote_db->nft_get_all_tokens();
nft_id_type lb_id;
if(lower_id)
lb_id = *lower_id;
return my->_remote_db->nft_get_all_tokens(lb_id, limit);
}
vector<nft_object> wallet_api::nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional<nft_id_type> lower_id) const
{
nft_id_type lb_id;
if(lower_id)
lb_id = *lower_id;
return my->_remote_db->nft_get_tokens_by_owner(owner, lb_id, limit);
}
vector<nft_metadata_object> wallet_api::nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional<nft_metadata_id_type> lower_id) const
{
nft_metadata_id_type lb_id;
if(lower_id)
lb_id = *lower_id;
return my->_remote_db->nft_get_metadata_by_owner(owner, lb_id, limit);
}
signed_transaction wallet_api::nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast )
@ -7270,10 +7294,6 @@ vector<account_role_object> wallet_api::get_account_roles_by_owner(string owner_
account_object owner_account = my->get_account(owner_account_id_or_name);
return my->_remote_db->get_account_roles_by_owner(owner_account.id);
}
// default ctor necessary for FC_REFLECT
signed_block_with_info::signed_block_with_info()
{
}
order_book wallet_api::get_order_book( const string& base, const string& quote, unsigned limit )
{
@ -7340,17 +7360,6 @@ std::string wallet_api::eth_estimate_withdrawal_transaction_fee() const
return my->eth_estimate_withdrawal_transaction_fee();
}
// default ctor necessary for FC_REFLECT
signed_block_with_info::signed_block_with_info( const signed_block& block )
: signed_block( block )
{
block_id = id();
signing_key = signee();
transaction_ids.reserve( transactions.size() );
for( const processed_transaction& tx : transactions )
transaction_ids.push_back( tx.id() );
}
vesting_balance_object_with_info::vesting_balance_object_with_info()
: vesting_balance_object()
{

View file

@ -9,28 +9,48 @@ using namespace graphene::peerplays_sidechain::ethereum;
BOOST_AUTO_TEST_SUITE(ethereum_transaction_tests)
BOOST_AUTO_TEST_CASE(withdrawal_encoder_test) {
const withdrawal_encoder encoder;
const auto tx = encoder.encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.36.0");
const auto tx = withdrawal_encoder::encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.36.0");
BOOST_CHECK_EQUAL(tx, "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33362E300000000000000000000000000000000000000000000000000000");
const auto tx1 = encoder.encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.36.1");
const auto tx1 = withdrawal_encoder::encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.36.1");
BOOST_CHECK_EQUAL(tx1, "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33362E310000000000000000000000000000000000000000000000000000");
}
BOOST_AUTO_TEST_CASE(withdrawal_signature_encoder_test) {
const signature_encoder encoder{withdrawal_function_signature};
encoded_sign_transaction transaction;
transaction.data = "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33362E300000000000000000000000000000000000000000000000000000";
transaction.sign = sign_hash(keccak_hash(transaction.data), "0x21", "eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060" );
const auto tx = encoder.encode({transaction});
BOOST_CHECK_EQUAL(tx, "0xdaac6c8100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000065c4622d2ff2b2d89c5c6f8225ab0f979bc69d4fcd4fd47db757b66fb8a39e2bc5522be5d101aa11e66da78db973f136b323be10bd107ff0b648f06b4c71ef2a4f00000000000000000000000000000000000000000000000000000000000000a4e088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33362E30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
}
BOOST_AUTO_TEST_CASE(update_owners_encoder_test) {
std::vector<std::pair<std::string, uint16_t>> owners_weights;
owners_weights.emplace_back("5FbBb31BE52608D2F52247E8400B7fCaA9E0bC12", 1);
owners_weights.emplace_back("76ce31bd03f601c3fc13732def921c5bac282676", 1);
const update_owners_encoder encoder;
const auto tx = encoder.encode(owners_weights, "1.35.0");
const auto tx = update_owners_encoder::encode(owners_weights, "1.35.0");
BOOST_CHECK_EQUAL(tx, "0x23ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33352E300000000000000000000000000000000000000000000000000000");
owners_weights.emplace_back("09ee460834498a4ee361beb819470061b7381b49", 1);
const auto tx1 = encoder.encode(owners_weights, "1.36.1");
const auto tx1 = update_owners_encoder::encode(owners_weights, "1.36.1");
BOOST_CHECK_EQUAL(tx1, "0x23ab6adf0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac282676000000000000000000000000000000000000000000000000000000000000000100000000000000000000000009ee460834498a4ee361beb819470061b7381b4900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33362E310000000000000000000000000000000000000000000000000000");
}
BOOST_AUTO_TEST_CASE(update_owners_signature_encoder_test) {
const signature_encoder encoder{update_owners_function_signature};
encoded_sign_transaction transaction;
transaction.data = "0x23ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33352E300000000000000000000000000000000000000000000000000000";
transaction.sign = sign_hash(keccak_hash(transaction.data), "0x21", "eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060" );
const auto tx = encoder.encode({transaction});
BOOST_CHECK_EQUAL(tx, "0x9d6086730000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000006698877eafa525c1a55f6b5e0a7187dfae484c97d9f77c4421a00276a9408a3e713d24402b44c05a883142fcffa84e1a802be37c17bb360f6f4810eb0415c8bbfd000000000000000000000000000000000000000000000000000000000000012423ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33352E30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
}
BOOST_AUTO_TEST_CASE(raw_transaction_serialization_test) {
raw_transaction raw_tr;
raw_tr.nonce = "0x0";

View file

@ -4,76 +4,204 @@
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/app/database_api.hpp>
using namespace graphene::chain;
using namespace graphene::chain::test;
class nft_test_helper
{
database_fixture& fixture_;
public:
nft_test_helper(database_fixture& fixture):
fixture_(fixture)
{
fixture_.generate_blocks(HARDFORK_NFT_TIME);
fixture_.generate_block();
fixture_.generate_block();
set_expiration(fixture_.db, fixture_.trx);
}
nft_metadata_object create_metadata(const std::string& name, const std::string& symbol, const std::string& uri, const account_id_type& owner, const fc::ecc::private_key &priv_key)
{
const auto& idx_by_id = fixture_.db.get_index_type<nft_metadata_index>().indices().get<by_id>();
size_t obj_count0 = idx_by_id.size();
fixture_.generate_block();
signed_transaction trx;
set_expiration(fixture_.db, trx);
nft_metadata_create_operation op;
op.owner = owner;
op.symbol = symbol;
op.base_uri = uri;
op.name = name;
op.is_transferable = true;
BOOST_CHECK_NO_THROW(op.validate());
trx.operations.push_back(op);
fixture_.sign(trx, priv_key);
PUSH_TX(fixture_.db, trx, ~0);
fixture_.generate_block();
BOOST_REQUIRE( idx_by_id.size() == obj_count0 + 1 ); // one more metadata created
const auto& idx_by_name = fixture_.db.get_index_type<nft_metadata_index>().indices().get<by_name>();
auto obj = idx_by_name.find(name);
BOOST_CHECK( obj->owner == owner );
BOOST_CHECK( obj->name == name );
BOOST_CHECK( obj->symbol == symbol );
BOOST_CHECK( obj->base_uri == uri );
return *obj;
}
nft_object mint(const nft_metadata_id_type& metadata, const account_id_type& owner, const account_id_type& payer,
const fc::optional<account_id_type>& approved, const std::vector<account_id_type>& approved_operators,
const fc::ecc::private_key &priv_key)
{
const auto& idx_by_id = fixture_.db.get_index_type<nft_index>().indices().get<by_id>();
size_t obj_count0 = idx_by_id.size();
fixture_.generate_block();
signed_transaction trx;
set_expiration(fixture_.db, trx);
nft_mint_operation op;
op.nft_metadata_id = metadata;
op.payer = payer;
op.owner = owner;
if (approved)
op.approved = *approved;
op.approved_operators = approved_operators;
trx.operations.push_back(op);
fixture_.sign(trx, priv_key);
PUSH_TX(fixture_.db, trx, ~0);
fixture_.generate_block();
BOOST_REQUIRE(idx_by_id.size() == obj_count0 + 1); // one more created
auto obj = idx_by_id.rbegin();
BOOST_REQUIRE(obj != idx_by_id.rend());
BOOST_CHECK(obj->owner == owner);
BOOST_CHECK(obj->approved_operators.size() == approved_operators.size());
BOOST_CHECK(obj->approved_operators == approved_operators);
return *obj;
}
};
BOOST_FIXTURE_TEST_SUITE( nft_tests, database_fixture )
BOOST_AUTO_TEST_CASE( nft_metadata_name_validation_test ) {
BOOST_TEST_MESSAGE("nft_metadata_name_validation_test");
ACTORS((mdowner));
nft_metadata_create_operation op;
op.owner = mdowner_id;
op.symbol = "NFT";
op.base_uri = "http://nft.example.com";
op.name = "123";
op.is_transferable = true;
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "1ab";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = ".abc";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "abc.";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "ABC";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abcdefghijklmnopq";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "ab";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "***";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "a12";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "a1b";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abc";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abc123defg12345";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "NFT Test";
BOOST_CHECK_NO_THROW(op.validate());
}
BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) {
BOOST_TEST_MESSAGE("nft_metadata_create_test");
generate_blocks(HARDFORK_NFT_TIME);
generate_block();
generate_block();
set_expiration(db, trx);
nft_test_helper nfth(*this);
ACTORS((mdowner));
nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key);
}
generate_block();
set_expiration(db, trx);
BOOST_AUTO_TEST_CASE( nft_metadata_listing_test ) {
BOOST_TEST_MESSAGE("nft_metadata_listing_test");
nft_test_helper nfth(*this);
ACTORS((mdowner1));
ACTORS((mdowner2));
// prepare metadata set
for (int i=0; i < 200; i++)
{
BOOST_TEST_MESSAGE("Send nft_metadata_create_operation");
nft_metadata_create_operation op;
op.owner = mdowner_id;
op.symbol = "NFT";
op.base_uri = "http://nft.example.com";
op.name = "123";
op.is_transferable = true;
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "1ab";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = ".abc";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "abc.";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "ABC";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abcdefghijklmnopq";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "ab";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "***";
BOOST_CHECK_THROW(op.validate(), fc::exception);
op.name = "a12";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "a1b";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abc";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "abc123defg12345";
BOOST_CHECK_NO_THROW(op.validate());
op.name = "NFT Test";
trx.operations.push_back(op);
sign(trx, mdowner_private_key);
PUSH_TX(db, trx, ~0);
string sfx = fc::to_pretty_string(i);
nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner1_id, mdowner1_private_key);
BOOST_REQUIRE(md.id == nft_metadata_id_type(i));
}
for (int i=200; i < 250; i++)
{
string sfx = fc::to_pretty_string(i);
nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner2_id, mdowner2_private_key);
BOOST_REQUIRE(md.id == nft_metadata_id_type(i));
}
generate_block();
BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results");
graphene::app::database_api db_api(db);
vector<nft_metadata_object> listed;
const auto& idx = db.get_index_type<nft_metadata_index>().indices().get<by_id>();
BOOST_REQUIRE( idx.size() == 1 );
auto obj = idx.begin();
BOOST_REQUIRE( obj != idx.end() );
BOOST_CHECK( obj->owner == mdowner_id );
BOOST_CHECK( obj->name == "NFT Test" );
BOOST_CHECK( obj->symbol == "NFT" );
BOOST_CHECK( obj->base_uri == "http://nft.example.com" );
// first 100 returned
listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 100);
BOOST_REQUIRE(listed.size() == 100);
BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 0));
BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(99));
// 100 starting from 50
listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(50), 100);
BOOST_REQUIRE(listed.size() == 100);
BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 50));
BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(149));
// the last 5 must be returned
listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(195), 10);
BOOST_REQUIRE(listed.size() == 5);
BOOST_REQUIRE(listed[0].id == nft_metadata_id_type(195));
BOOST_REQUIRE(listed[4].id == nft_metadata_id_type(199));
// too much requested at once
BOOST_CHECK_THROW(db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 101), fc::exception);
// the last 40 must be returned
listed = db_api.nft_get_metadata_by_owner(mdowner2_id, nft_metadata_id_type(210), 100);
BOOST_REQUIRE(listed.size() == 40);
BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type(210));
BOOST_REQUIRE(listed[39].id == nft_metadata_id_type(249));
}
@ -120,49 +248,112 @@ BOOST_AUTO_TEST_CASE( nft_mint_test ) {
generate_block();
set_expiration(db, trx);
INVOKE(nft_metadata_create_test);
nft_test_helper nfth(*this);
ACTORS((mdowner));
ACTORS((alice));
ACTORS((bob));
ACTORS((operator1));
ACTORS((operator2));
GET_ACTOR(mdowner);
nft_metadata_object md = nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key);
generate_block();
set_expiration(db, trx);
nfth.mint(md.id, alice_id, mdowner_id, alice_id, {operator1_id, operator2_id}, alice_private_key);
}
BOOST_AUTO_TEST_CASE( nft_object_listing_test ) {
BOOST_TEST_MESSAGE("nft_object_listing_test");
nft_test_helper nfth(*this);
ACTORS((mdowner1));
ACTORS((mdowner2));
ACTORS((alice));
ACTORS((bob));
nft_metadata_object md1 = nfth.create_metadata("NFT Test 1", "NFT1", "http://nft.example.com", mdowner1_id, mdowner1_private_key);
nft_metadata_object md2 = nfth.create_metadata("NFT Test 2", "NFT2", "http://nft.example.com", mdowner2_id, mdowner2_private_key);
// create NFT objects: 200 owned by alice and 200 by bob
for (int i=0; i < 200; i++)
{
BOOST_TEST_MESSAGE("Send nft_mint_operation");
const auto& idx = db.get_index_type<nft_metadata_index>().indices().get<by_id>();
BOOST_REQUIRE( idx.size() == 1 );
auto nft_md_obj = idx.begin();
nft_mint_operation op;
op.payer = mdowner_id;
op.nft_metadata_id = nft_md_obj->id;
op.owner = alice_id;
op.approved = alice_id;
op.approved_operators.push_back(operator1_id);
op.approved_operators.push_back(operator2_id);
trx.operations.push_back(op);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
nft_object nft = nfth.mint(md1.id, alice_id, mdowner1_id, alice_id, {}, alice_private_key);
BOOST_REQUIRE(nft.id == nft_id_type(i));
}
for (int i=200; i < 250; i++)
{
nft_object nft = nfth.mint(md1.id, bob_id, mdowner1_id, bob_id, {}, bob_private_key);
BOOST_REQUIRE(nft.id == nft_id_type(i));
}
generate_block();
BOOST_TEST_MESSAGE("Check nft_mint_operation results");
graphene::app::database_api db_api(db);
vector<nft_object> listed;
const auto& idx = db.get_index_type<nft_index>().indices().get<by_id>();
BOOST_REQUIRE( idx.size() == 1 );
auto obj = idx.begin();
BOOST_REQUIRE( obj != idx.end() );
BOOST_CHECK( obj->owner == alice_id );
BOOST_CHECK( obj->approved_operators.size() == 2 );
BOOST_CHECK( obj->approved_operators.at(0) == operator1_id );
BOOST_CHECK( obj->approved_operators.at(1) == operator2_id );
//
// listing all tokens:
//
// first 100 returned, all alice's
listed = db_api.nft_get_all_tokens(nft_id_type(0), 100);
BOOST_REQUIRE(listed.size() == 100);
BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0));
BOOST_REQUIRE(listed[99].id == nft_id_type(99));
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; }));
// 100 starting from 50, all alice's
listed = db_api.nft_get_all_tokens(nft_id_type(50), 100);
BOOST_REQUIRE(listed.size() == 100);
BOOST_REQUIRE(listed[ 0].id == nft_id_type( 50));
BOOST_REQUIRE(listed[99].id == nft_id_type(149));
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; }));
// the last 5 must be returned, all bob's
listed = db_api.nft_get_all_tokens(nft_id_type(245), 10);
BOOST_REQUIRE(listed.size() == 5);
BOOST_REQUIRE(listed[0].id == nft_id_type(245));
BOOST_REQUIRE(listed[4].id == nft_id_type(249));
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; }));
// 10 from the middle of the set, half alice's, half bob's
listed = db_api.nft_get_all_tokens(nft_id_type(195), 10);
BOOST_REQUIRE(listed.size() == 10);
BOOST_REQUIRE(listed[0].id == nft_id_type(195));
BOOST_REQUIRE(listed[9].id == nft_id_type(204));
BOOST_REQUIRE(listed[0].owner == alice_id);
BOOST_REQUIRE(listed[4].owner == alice_id);
BOOST_REQUIRE(listed[5].owner == bob_id);
BOOST_REQUIRE(listed[9].owner == bob_id);
// too much requested at once
BOOST_CHECK_THROW(db_api.nft_get_all_tokens(nft_id_type(0), 101), fc::exception);
//
// listing tokens by owner:
//
// first 100 alice's
listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 100);
BOOST_REQUIRE(listed.size() == 100);
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; }));
BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0));
BOOST_REQUIRE(listed[99].id == nft_id_type(99));
// the last 5 alice's must be returned
listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(195), 10);
BOOST_REQUIRE(listed.size() == 5);
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; }));
BOOST_REQUIRE(listed[0].id == nft_id_type(195));
BOOST_REQUIRE(listed[4].id == nft_id_type(199));
// all 50 bob's
listed = db_api.nft_get_tokens_by_owner(bob_id, nft_id_type(0), 60);
BOOST_REQUIRE(listed.size() == 50);
BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; }));
BOOST_REQUIRE(listed[ 0].id == nft_id_type(200));
BOOST_REQUIRE(listed[49].id == nft_id_type(249));
// too much requested at once
BOOST_CHECK_THROW(db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 101), fc::exception);
}