mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 11:12:16 +00:00
Soft send message validation (#1021)
* check mode on invalid action_send_msg * Fix random seed generation * Explicitly skip invalid actions * Count skipped valid messages, rename cfg option to message_skip_enabled * Allow unfreeze via external messages * Detect and handle bounce_on_fail mode for invalid messages * Fix codestyle * Adjust doc
This commit is contained in:
parent
6250662d56
commit
bd23029d0a
5 changed files with 86 additions and 21 deletions
|
@ -19,6 +19,6 @@
|
|||
namespace ton {
|
||||
|
||||
// See doc/GlobalVersions.md
|
||||
const int SUPPORTED_VERSION = 7;
|
||||
const int SUPPORTED_VERSION = 8;
|
||||
|
||||
}
|
||||
|
|
|
@ -1285,7 +1285,11 @@ bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputeP
|
|||
// if the smart contract wants to randomize further, it can use RANDOMIZE instruction
|
||||
td::BitArray<256 + 256> data;
|
||||
data.bits().copy_from(cfg.block_rand_seed.cbits(), 256);
|
||||
(data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256);
|
||||
if (cfg.global_version >= 8) {
|
||||
(data.bits() + 256).copy_from(account.addr.cbits(), 256);
|
||||
} else {
|
||||
(data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256);
|
||||
}
|
||||
rand_seed.clear();
|
||||
data.compute_sha256(rand_seed);
|
||||
return true;
|
||||
|
@ -1600,12 +1604,22 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
|
|||
cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state;
|
||||
return true;
|
||||
} else if (in_msg_state.not_null()) {
|
||||
if (cfg.allow_external_unfreeze) {
|
||||
if (in_msg_extern && account.addr != in_msg_state->get_hash().bits()) {
|
||||
// only for external messages with non-zero initstate in active accounts
|
||||
LOG(DEBUG) << "in_msg_state hash mismatch in external message";
|
||||
cp.skip_reason = ComputePhase::sk_bad_state;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
unpack_msg_state(cfg, true); // use only libraries
|
||||
}
|
||||
if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) {
|
||||
LOG(DEBUG) << "in_msg_state hash mismatch in external message";
|
||||
cp.skip_reason = ComputePhase::sk_bad_state;
|
||||
return true;
|
||||
if (!cfg.allow_external_unfreeze) {
|
||||
if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) {
|
||||
LOG(DEBUG) << "in_msg_state hash mismatch in external message";
|
||||
cp.skip_reason = ComputePhase::sk_bad_state;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
td::optional<PrecompiledContractsConfig::Contract> precompiled;
|
||||
|
@ -1823,16 +1837,40 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) {
|
|||
|
||||
ap.tot_actions = n;
|
||||
ap.spec_actions = ap.skipped_actions = 0;
|
||||
std::vector<Ref<vm::Cell>> non_skipped_action_list;
|
||||
for (int i = n - 1; i >= 0; --i) {
|
||||
ap.result_arg = n - 1 - i;
|
||||
if (!block::gen::t_OutListNode.validate_ref(ap.action_list[i])) {
|
||||
if (cfg.message_skip_enabled) {
|
||||
// try to read mode from action_send_msg even if out_msg scheme is violated
|
||||
// action should at least contain 40 bits: 32bit tag and 8 bit mode
|
||||
// if (mode & 2), that is ignore error mode, skip action even for invalid message
|
||||
// if there is no (mode & 2) but (mode & 16) presents - enable bounce if possible
|
||||
bool special = true;
|
||||
auto cs = load_cell_slice_special(ap.action_list[i], special);
|
||||
if (!special) {
|
||||
if ((cs.size() >= 40) && ((int)cs.fetch_ulong(32) == 0x0ec3c86d)) {
|
||||
int mode = (int)cs.fetch_ulong(8);
|
||||
if (mode & 2) {
|
||||
ap.skipped_actions++;
|
||||
continue;
|
||||
} else if ((mode & 16) && cfg.bounce_on_fail_enabled) {
|
||||
ap.bounce = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ap.result_code = 34; // action #i invalid or unsupported
|
||||
ap.action_list_invalid = true;
|
||||
LOG(DEBUG) << "invalid action " << ap.result_arg << " found while preprocessing action list: error code "
|
||||
<< ap.result_code;
|
||||
return true;
|
||||
} else {
|
||||
non_skipped_action_list.push_back(ap.action_list[i]);
|
||||
}
|
||||
}
|
||||
ap.action_list = std::move(non_skipped_action_list);
|
||||
n -= ap.skipped_actions;
|
||||
ap.valid = true;
|
||||
for (int i = n - 1; i >= 0; --i) {
|
||||
ap.result_arg = n - 1 - i;
|
||||
|
@ -2280,6 +2318,15 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
return -1;
|
||||
}
|
||||
bool skip_invalid = (act_rec.mode & 2);
|
||||
auto check_skip_invalid = [&](unsigned error_code) -> unsigned int {
|
||||
if (skip_invalid) {
|
||||
if (cfg.message_skip_enabled) {
|
||||
ap.skipped_actions++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return error_code;
|
||||
};
|
||||
// try to parse suggested message in act_rec.out_msg
|
||||
td::RefInt256 fwd_fee, ihr_fee;
|
||||
block::gen::MessageRelaxed::Record msg;
|
||||
|
@ -2363,7 +2410,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
bool to_mc = false;
|
||||
if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc)) {
|
||||
LOG(DEBUG) << "invalid destination address in a proposed outbound message";
|
||||
return skip_invalid ? 0 : 36; // invalid destination address
|
||||
return check_skip_invalid(36); // invalid destination address
|
||||
}
|
||||
|
||||
// fetch message pricing info
|
||||
|
@ -2378,7 +2425,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (!ext_msg && !(act_rec.mode & 0x80) && !(act_rec.mode & 1)) {
|
||||
if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
|
||||
LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message";
|
||||
return skip_invalid ? 0 : 37;
|
||||
return check_skip_invalid(37);
|
||||
}
|
||||
block::CurrencyCollection value;
|
||||
CHECK(value.unpack(info.value));
|
||||
|
@ -2395,7 +2442,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (new_funds->sgn() < 0) {
|
||||
LOG(DEBUG)
|
||||
<< "not enough value to transfer with the message: all of the inbound message value has been consumed";
|
||||
return skip_invalid ? 0 : 37;
|
||||
return check_skip_invalid(37);
|
||||
}
|
||||
}
|
||||
funds = std::min(funds, new_funds);
|
||||
|
@ -2433,17 +2480,17 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (sstat.cells > max_cells && max_cells < cfg.size_limits.max_msg_cells) {
|
||||
LOG(DEBUG) << "not enough funds to process a message (max_cells=" << max_cells << ")";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 40;
|
||||
return check_skip_invalid(40);
|
||||
}
|
||||
if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > max_cells) {
|
||||
LOG(DEBUG) << "message too large, invalid";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 40;
|
||||
return check_skip_invalid(40);
|
||||
}
|
||||
if (max_merkle_depth > max_allowed_merkle_depth) {
|
||||
LOG(DEBUG) << "message has too big merkle depth, invalid";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 40;
|
||||
return check_skip_invalid(40);
|
||||
}
|
||||
LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits";
|
||||
|
||||
|
@ -2475,7 +2522,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
|
||||
LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 37;
|
||||
return check_skip_invalid(37);
|
||||
}
|
||||
if (info.ihr_disabled) {
|
||||
// if IHR is disabled, IHR fees will be always zero
|
||||
|
@ -2502,7 +2549,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
LOG(DEBUG)
|
||||
<< "not enough value to transfer with the message: all of the inbound message value has been consumed";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 37;
|
||||
return check_skip_invalid(37);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2518,7 +2565,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
LOG(DEBUG) << "not enough value attached to the message to pay forwarding fees : have " << req.grams << ", need "
|
||||
<< fees_total;
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 37; // not enough grams
|
||||
return check_skip_invalid(37); // not enough grams
|
||||
} else {
|
||||
// decrease message value
|
||||
req.grams -= fees_total;
|
||||
|
@ -2529,7 +2576,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
LOG(DEBUG) << "not enough grams to transfer with the message : remaining balance is "
|
||||
<< ap.remaining_balance.to_str() << ", need " << req_grams_brutto << " (including forwarding fees)";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 37; // not enough grams
|
||||
return check_skip_invalid(37); // not enough grams
|
||||
}
|
||||
|
||||
Ref<vm::Cell> new_extra;
|
||||
|
@ -2539,7 +2586,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
<< block::CurrencyCollection{0, req.extra}.to_str() << " required, only "
|
||||
<< block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " available";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 38; // not enough (extra) funds
|
||||
return check_skip_invalid(38); // not enough (extra) funds
|
||||
}
|
||||
if (ap.remaining_balance.extra.not_null() || req.extra.not_null()) {
|
||||
LOG(DEBUG) << "subtracting extra currencies: "
|
||||
|
@ -2563,7 +2610,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
|
||||
if (redoing == 2) {
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 39;
|
||||
return check_skip_invalid(39);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
@ -2588,7 +2635,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (ap.remaining_balance.grams < fwd_fee) {
|
||||
LOG(DEBUG) << "not enough funds to pay for an outbound external message";
|
||||
collect_fine();
|
||||
return skip_invalid ? 0 : 37; // not enough grams
|
||||
return check_skip_invalid(37); // not enough grams
|
||||
}
|
||||
// repack message
|
||||
// ext_out_msg_info$11 constructor of CommonMsgInfo
|
||||
|
@ -2603,7 +2650,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
|
||||
if (redoing == 2) {
|
||||
collect_fine();
|
||||
return (skip_invalid ? 0 : 39);
|
||||
return check_skip_invalid(39);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
@ -3684,6 +3731,7 @@ td::Status FetchConfigParams::fetch_config_params(
|
|||
compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now);
|
||||
compute_phase_cfg->size_limits = size_limits;
|
||||
compute_phase_cfg->precompiled_contracts = config.get_precompiled_contracts_config();
|
||||
compute_phase_cfg->allow_external_unfreeze = compute_phase_cfg->global_version >= 8;
|
||||
}
|
||||
{
|
||||
// compute action_phase_cfg
|
||||
|
@ -3707,6 +3755,7 @@ td::Status FetchConfigParams::fetch_config_params(
|
|||
action_phase_cfg->size_limits = size_limits;
|
||||
action_phase_cfg->action_fine_enabled = config.get_global_version() >= 4;
|
||||
action_phase_cfg->bounce_on_fail_enabled = config.get_global_version() >= 4;
|
||||
action_phase_cfg->message_skip_enabled = config.get_global_version() >= 8;
|
||||
action_phase_cfg->mc_blackhole_addr = config.get_burning_config().blackhole_addr;
|
||||
}
|
||||
{
|
||||
|
|
|
@ -126,6 +126,7 @@ struct ComputePhaseConfig {
|
|||
bool stop_on_accept_message = false;
|
||||
PrecompiledContractsConfig precompiled_contracts;
|
||||
bool dont_run_precompiled_ = false;
|
||||
bool allow_external_unfreeze{false};
|
||||
|
||||
ComputePhaseConfig() : gas_price(0), gas_limit(0), special_gas_limit(0), gas_credit(0) {
|
||||
compute_threshold();
|
||||
|
@ -163,6 +164,7 @@ struct ActionPhaseConfig {
|
|||
const WorkchainSet* workchains{nullptr};
|
||||
bool action_fine_enabled{false};
|
||||
bool bounce_on_fail_enabled{false};
|
||||
bool message_skip_enabled{false};
|
||||
td::optional<td::Bits256> mc_blackhole_addr;
|
||||
const MsgPrices& fetch_msg_prices(bool is_masterchain) const {
|
||||
return is_masterchain ? fwd_mc : fwd_std;
|
||||
|
|
|
@ -96,4 +96,16 @@ Operations for working with Merkle proofs, where cells can have non-zero level a
|
|||
|
||||
### Other changes
|
||||
* `GLOBALID` gets `ConfigParam 19` from the tuple, not from the config dict. This decreases gas usage.
|
||||
* `SENDMSG` gets `ConfigParam 24/25` (message prices) from the tuple, not from the config dict, and also uses `ConfigParam 43` to get max_msg_cells.
|
||||
* `SENDMSG` gets `ConfigParam 24/25` (message prices) from the tuple, not from the config dict, and also uses `ConfigParam 43` to get max_msg_cells.
|
||||
|
||||
|
||||
## Version 7
|
||||
|
||||
[Explicitly nullify](https://github.com/ton-blockchain/ton/pull/957/files) `due_payment` after due reimbursment.
|
||||
|
||||
## Version 8
|
||||
|
||||
- Check mode on invalid `action_send_msg`. Ignore action if `IGNORE_ERROR` (+2) bit is set, bounce if `BOUNCE_ON_FAIL` (+16) bit is set.
|
||||
- Slightly change random seed generation to fix mix of `addr_rewrite` and `addr`.
|
||||
- Fill in `skipped_actions` for both invalid and valid messages with `IGNORE_ERROR` mode that can't be sent.
|
||||
- Allow unfreeze through external messages.
|
||||
|
|
|
@ -967,6 +967,7 @@ bool ValidateQuery::fetch_config_params() {
|
|||
compute_phase_cfg_.suspended_addresses = config_->get_suspended_addresses(now_);
|
||||
compute_phase_cfg_.size_limits = size_limits;
|
||||
compute_phase_cfg_.precompiled_contracts = config_->get_precompiled_contracts_config();
|
||||
compute_phase_cfg_.allow_external_unfreeze = compute_phase_cfg_.global_version >= 8;
|
||||
}
|
||||
{
|
||||
// compute action_phase_cfg
|
||||
|
@ -990,6 +991,7 @@ bool ValidateQuery::fetch_config_params() {
|
|||
action_phase_cfg_.size_limits = size_limits;
|
||||
action_phase_cfg_.action_fine_enabled = config_->get_global_version() >= 4;
|
||||
action_phase_cfg_.bounce_on_fail_enabled = config_->get_global_version() >= 4;
|
||||
action_phase_cfg_.message_skip_enabled = config_->get_global_version() >= 8;
|
||||
action_phase_cfg_.mc_blackhole_addr = config_->get_burning_config().blackhole_addr;
|
||||
}
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue