/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2019-2020 Telegram Systems LLP */ #pragma once #include "td/utils/Variant.h" #include "td/utils/Status.h" #include "vm/cells/Cell.h" #include "vm/cells/CellSlice.h" #include "vm/cells/CellString.h" #include "smc-envelope/SmartContract.h" #include "Ed25519.h" #include "common/checksum.h" #include namespace ton { const td::Bits256 DNS_NEXT_RESOLVER_CATEGORY = td::sha256_bits256(td::Slice("dns_next_resolver", strlen("dns_next_resolver"))); class DnsInterface { public: struct EntryDataText { std::string text; bool operator==(const EntryDataText& other) const { return text == other.text; } }; struct EntryDataNextResolver { block::StdAddress resolver; bool operator==(const EntryDataNextResolver& other) const { return resolver == other.resolver; } }; struct EntryDataAdnlAddress { ton::Bits256 adnl_address; // TODO: proto bool operator==(const EntryDataAdnlAddress& other) const { return adnl_address == other.adnl_address; } }; struct EntryDataSmcAddress { block::StdAddress smc_address; bool operator==(const EntryDataSmcAddress& other) const { return smc_address == other.smc_address; } // TODO: capability }; struct EntryDataStorageAddress { ton::Bits256 bag_id; // TODO: proto bool operator==(const EntryDataStorageAddress& other) const { return bag_id == other.bag_id; } }; struct EntryData { enum Type { Empty, Text, NextResolver, AdnlAddress, SmcAddress, StorageAddress } type{Empty}; td::Variant data; static EntryData text(std::string text) { return {Text, EntryDataText{text}}; } static EntryData next_resolver(block::StdAddress resolver) { return {NextResolver, EntryDataNextResolver{resolver}}; } static EntryData adnl_address(ton::Bits256 adnl_address) { return {AdnlAddress, EntryDataAdnlAddress{adnl_address}}; } static EntryData smc_address(block::StdAddress smc_address) { return {SmcAddress, EntryDataSmcAddress{smc_address}}; } static EntryData storage_address(ton::Bits256 bag_id) { return {StorageAddress, EntryDataStorageAddress{bag_id}}; } bool operator==(const EntryData& other) const { return data == other.data; } friend td::StringBuilder& operator<<(td::StringBuilder& sb, const EntryData& data); td::Result> as_cell() const; static td::Result from_cellslice(vm::CellSlice& cs); }; struct Entry { std::string name; td::Bits256 category; EntryData data; bool partially_resolved = false; auto key() const { return std::tie(name, category); } bool operator<(const Entry& other) const { return key() < other.key(); } bool operator==(const Entry& other) const { return key() == other.key() && data == other.data; } friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { sb << entry.name << ":" << entry.category.to_hex() << ":" << entry.data; return sb; } }; struct RawEntry { std::string name; td::Bits256 category; td::Ref data; bool partially_resolved = false; }; struct ActionExt { std::string name; td::Bits256 category; td::optional data; static td::Result parse(td::Slice); }; struct Action { std::string name; td::Bits256 category; td::optional> data; bool does_create_category() const { CHECK(!name.empty()); CHECK(!category.is_zero()); return static_cast(data); } bool does_change_empty() const { CHECK(!name.empty()); CHECK(!category.is_zero()); return static_cast(data) && data.value().not_null(); } void make_non_empty() { CHECK(!name.empty()); CHECK(!category.is_zero()); if (!data) { data = td::Ref(); } } friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& action) { sb << action.name << ":" << action.category.to_hex() << ":"; if (action.data) { if (action.data.value().is_null()) { sb << ""; } else { sb << ""; } } else { sb << ""; } return sb; } }; virtual ~DnsInterface() = default; virtual size_t get_max_name_size() const = 0; virtual td::Result> resolve_raw(td::Slice name, td::Bits256 category) const = 0; virtual td::Result> create_update_query( td::Ed25519::PrivateKey& pk, td::Span actions, td::uint32 valid_until = std::numeric_limits::max()) const = 0; td::Result> resolve(td::Slice name, td::Bits256 category) const; static std::string encode_name(td::Slice name); static std::string decode_name(td::Slice name); static size_t get_default_max_name_size() { return 128; } static SmartContract::Args resolve_args_raw(td::Slice encoded_name, td::Bits256 category, block::StdAddress address = {}); static td::Result resolve_args(td::Slice name, td::Bits256 category, block::StdAddress address = {}); }; class ManualDns : public ton::SmartContract, public DnsInterface { public: ManualDns(State state, block::StdAddress address = {}) : SmartContract(std::move(state)), address_(std::move(address)) { } ManualDns* make_copy() const override { return new ManualDns{state_}; } // creation static td::Ref create(State state, block::StdAddress address = {}) { return td::Ref(true, std::move(state), std::move(address)); } static td::Ref create(td::Ref data = {}, int revision = 0); static td::Ref create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, int revision = 0); static std::string serialize_data(const EntryData& data); static td::Result> parse_data(td::Slice cmd); static td::Result parse_line(td::Slice cmd); static td::Result> parse(td::Slice cmd); static td::optional guess_revision(const vm::Cell::Hash& code_hash); static td::optional guess_revision(const block::StdAddress& address, const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); td::Ref create_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 valid_until) const { return create_init_data_fast(public_key, valid_until); } td::Result get_wallet_id() const; td::Result get_wallet_id_or_throw() const; td::Result> create_set_value_unsigned(td::Bits256 category, td::Slice name, td::Ref data) const; td::Result> create_delete_value_unsigned(td::Bits256 category, td::Slice name) const; td::Result> create_delete_all_unsigned() const; td::Result> create_set_all_unsigned(td::Span entries) const; td::Result> create_delete_name_unsigned(td::Slice name) const; td::Result> create_set_name_unsigned(td::Slice name, td::Span entries) const; td::Result> prepare(td::Ref data, td::uint32 valid_until) const; static td::Result> sign(const td::Ed25519::PrivateKey& private_key, td::Ref data); static td::Ref create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); size_t get_max_name_size() const override; td::Result> resolve_raw(td::Slice name, td::Bits256 category) const override; td::Result> resolve_raw_or_throw(td::Slice name, td::Bits256 category) const; td::Result> create_init_query( const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until = std::numeric_limits::max()) const; td::Result> create_update_query( td::Ed25519::PrivateKey& pk, td::Span actions, td::uint32 valid_until = std::numeric_limits::max()) const override; template struct CombinedActions { std::string name; td::Bits256 category = td::Bits256::zero(); td::optional> actions; friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CombinedActions& action) { sb << action.name << ":" << action.category.to_hex() << ":"; if (action.actions) { sb << "" << action.actions.value().size(); } else { sb << ""; } return sb; } }; template static std::vector> combine_actions(td::Span actions) { struct Info { std::set known_category; std::vector actions; bool closed{false}; bool non_empty{false}; }; std::map mp; std::vector> res; for (auto& action : td::reversed(actions)) { if (action.name.empty()) { CombinedActions set_all; set_all.actions = std::vector(); for (auto& it : mp) { for (auto& e : it.second.actions) { if (e.does_create_category()) { set_all.actions.value().push_back(std::move(e)); } } } res.push_back(std::move(set_all)); return res; } Info& info = mp[action.name]; if (info.closed) { continue; } if (!action.category.is_zero() && action.does_create_category()) { info.non_empty = true; } if (!info.known_category.insert(action.category).second) { continue; } if (action.category.is_zero()) { info.closed = true; auto old_actions = std::move(info.actions); bool is_empty = true; for (auto& action : old_actions) { if (is_empty && action.does_create_category()) { info.actions.push_back(std::move(action)); is_empty = false; } else if (!is_empty && action.does_change_empty()) { info.actions.push_back(std::move(action)); } } } else { info.actions.push_back(std::move(action)); } } for (auto& it : mp) { auto& info = it.second; if (info.closed) { CombinedActions ca; ca.name = it.first; ca.category = td::Bits256::zero(); if (!info.actions.empty() || info.non_empty) { ca.actions = std::move(info.actions); } res.push_back(std::move(ca)); } else { bool need_non_empty = info.non_empty; for (auto& a : info.actions) { if (need_non_empty) { a.make_non_empty(); need_non_empty = false; } CombinedActions ca; ca.name = a.name; ca.category = a.category; ca.actions = std::vector(); ca.actions.value().push_back(std::move(a)); res.push_back(ca); } } } return res; } td::Result> create_update_query(CombinedActions& combined) const; private: block::StdAddress address_; }; } // namespace ton