Skip to content

Commit 0899bce

Browse files
committed
epoch 710 upgrade
1 parent 69a2a6e commit 0899bce

12 files changed

Lines changed: 377 additions & 161 deletions

File tree

ex/lib/api/db_chain.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ defmodule DB.Chain do
179179

180180
# revert mmr
181181
case DB.MMR.snapshot_for(current_entry.hash, %{rtx: rtx}) do
182-
nil -> :ok
182+
nil -> raise "rewind: missing MMR snapshot for entry #{Base58.encode(current_entry.hash)} (height #{current_entry.header.height}); cannot restore accumulator"
183183
prev -> DB.MMR.save(prev, %{rtx: rtx})
184184
end
185185

@@ -200,6 +200,7 @@ defmodule DB.Chain do
200200
cf_table = case mut.table do
201201
"contractstate" -> cf.contractstate
202202
"contractstate_tree" -> cf.contractstate_tree
203+
"contractstate_tree_hbsmt" -> cf.contractstate_tree_hbsmt
203204
end
204205
case op do
205206
:put ->

ex/lib/api/db_entry.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ defmodule DB.Entry do
9090

9191
def apply_into_main_chain(entry, muts_hash, muts_rev, receipts, root_receipts, root_contractstate, db_opts = %{rtx: _}) do
9292
prev_mmr = DB.MMR.load_or_empty(db_opts)
93+
if prev_mmr.size != entry.header.height do
94+
raise "apply_into_main_chain: MMR size #{prev_mmr.size} != entry height #{entry.header.height}; entry is not the next block above the root, refusing to append"
95+
end
9396
DB.MMR.snapshot_before(entry.hash, prev_mmr, db_opts)
9497
new_mmr = MMR.append(prev_mmr, entry.hash)
9598
DB.MMR.save(new_mmr, db_opts)

ex/lib/api/db_mmr.ex

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,15 @@ defmodule DB.MMR do
7878
end
7979

8080
def export_snapshot(entry_hash, db_opts \\ %{}) do
81-
state = snapshot_for(entry_hash, db_opts)
82-
%{
83-
@sysconf_peaks => RDB.vecpak_encode(state.peaks),
84-
@sysconf_size => Integer.to_string(state.size)
85-
}
81+
case snapshot_for(entry_hash, db_opts) do
82+
nil ->
83+
raise "export_snapshot: missing MMR snapshot for rooted entry #{Base58.encode(entry_hash)}; refusing to ship a bundle without MMR state"
84+
state ->
85+
%{
86+
@sysconf_peaks => RDB.vecpak_encode(state.peaks),
87+
@sysconf_size => Integer.to_string(state.size)
88+
}
89+
end
8690
end
8791

8892
@doc """

ex/lib/consensus/coordination/fabric_snapshot.ex

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,17 +262,28 @@ defmodule FabricSnapshot do
262262
:ok
263263
end
264264

265-
defp verify_rooted_entry!(entry) do
266-
res =
267-
if entry[:mask] do
268-
hash = :crypto.hash(:sha256, RDB.vecpak_encode(entry.header))
269-
if entry[:hash] == hash, do: %{error: :ok}, else: %{error: :rooted_tip_hash_mismatch}
270-
else
271-
Entry.validate_signature(entry, true)
272-
end
265+
defp verify_rooted_entry!(entry, rtx) do
266+
res = if entry[:mask] do
267+
hash = :crypto.hash(:sha256, RDB.vecpak_encode(entry.header))
268+
if entry[:hash] == hash, do: %{error: :ok}, else: %{error: :rooted_tip_hash_mismatch}
269+
else
270+
Entry.validate_signature(entry, true)
271+
end
273272
if res.error != :ok do
274273
raise "bundle rooted tip failed verification: #{inspect res.error}"
275274
end
275+
276+
if entry.header.height >= Entry.root_chain_height() do
277+
mmr = DB.MMR.load_or_empty(%{rtx: rtx})
278+
if mmr.size != entry.header.height do
279+
raise "bundle rooted tip MMR size #{mmr.size} != entry height #{entry.header.height}"
280+
end
281+
case Entry.check_root_chain(entry.header, mmr) do
282+
:ok -> :ok
283+
{:mismatch, ours, theirs} ->
284+
raise "bundle rooted tip root_chain mismatch at height #{entry.header.height}; ours=#{Base58.encode(ours)} theirs=#{theirs && Base58.encode(theirs)}"
285+
end
286+
end
276287
:ok
277288
end
278289

@@ -392,7 +403,7 @@ defmodule FabricSnapshot do
392403
entry = Entry.unpack_from_db(entry_packed)
393404
height = entry.header.height
394405

395-
verify_rooted_entry!(entry)
406+
verify_rooted_entry!(entry, rtx)
396407

397408
DB.Entry.insert(entry, %{rtx: rtx})
398409
DB.Entry.apply_into_main_chain(entry, muts_hash, muts_rev, receipts,

ex/lib/consensus/models/entry.ex

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,6 @@ defmodule Entry do
5353
h (entry_hash || state_root || receipts_logs_extra_root)
5454
"""
5555

56-
# :root_chain — PHASE 1 schema-only rollout.
57-
# The field is whitelisted in @fields_header on every node, but proposers
58-
# do NOT populate it yet (see build_next/3 below). Because Map.take only
59-
# picks keys that exist on the source, current blocks (which don't carry
60-
# the key) hash identically to pre-rollout. Once enough peers have shipped
61-
# Phase 1, the next release flips proposers on (Phase 2). Old nodes that
62-
# missed Phase 1 will reject Phase-2 blocks.
6356
@fields [:header, :hash, :signature, :txs, :mask, :mask_size, :mask_set_size]
6457
@fields_header [:height, :prev_hash, :slot, :prev_slot, :signer, :dr, :vr, :root_tx, :root_validator, :root_chain]
6558

@@ -146,15 +139,19 @@ defmodule Entry do
146139
if !is_list(e.txs), do: throw(%{error: :txs_not_list})
147140
if length(e.txs) > 100, do: throw(%{error: :TEMPORARY_txs_only_100_per_entry})
148141

142+
# Duplicate TX check
143+
tx_hashes = Enum.map(e.txs, & &1.hash)
144+
if length(tx_hashes) != length(Enum.uniq(tx_hashes)), do: throw(%{error: :duplicate_tx_in_entry})
145+
149146
if !is_binary(eh.root_tx), do: throw(%{error: :root_tx_not_binary})
150147
if byte_size(eh.root_tx) != 32, do: throw(%{error: :root_tx_not_256_bits})
151-
if eh.root_tx != root_tx(Enum.map(e.txs, & &1.hash)), do: throw(%{error: :root_tx_invalid})
148+
if eh.root_tx != root_tx(tx_hashes, eh.height), do: throw(%{error: :root_tx_invalid})
152149

153150
if !is_binary(eh.root_validator), do: throw(%{error: :root_validator_not_binary})
154151
if byte_size(eh.root_validator) != 32, do: throw(%{error: :root_validator_not_256_bits})
155152
validators = DB.Chain.validators_for_height(eh.height)
156153
validators_last_change_height = DB.Chain.validators_last_change_height(eh.height)
157-
if eh.root_validator != root_validator(validators, validators_last_change_height), do: throw(%{error: :root_validator_invalid})
154+
if eh.root_validator != root_validator(validators, validators_last_change_height, eh.height), do: throw(%{error: :root_validator_invalid})
158155

159156
is_special_meeting_block = !!e[:mask]
160157
steam = Task.async_stream(e.txs, fn txu ->
@@ -213,6 +210,13 @@ defmodule Entry do
213210
end
214211
end
215212

213+
def root_chain_height(), do: RDBProtocol.forkheight()
214+
215+
def check_root_chain(header, mmr) do
216+
ours = MMR.root_chain(DB.MMR.chain_id(), mmr)
217+
if header.root_chain == ours, do: :ok, else: {:mismatch, ours, header.root_chain}
218+
end
219+
216220
def validate_next(cur_entry, next_entry) do
217221
try do
218222
ceh = cur_entry.header
@@ -224,26 +228,15 @@ defmodule Entry do
224228
if :crypto.hash(:sha256, ceh.dr) != neh.dr, do: throw(%{error: :invalid_dr})
225229
if !BlsEx.verify?(neh.signer, neh.vr, ceh.vr, BLS12AggSig.dst_vrf()), do: throw(%{error: :invalid_vr})
226230

227-
# root_chain soft validation (log-only for now). Only runs when the
228-
# proposer set the field AND our local MMR is in sync at this height.
229-
# Filters bad blocks out of the candidate pool BEFORE expensive
230-
# contract exec in apply_entry — so a persistently bad block doesn't
231-
# DoS our apply path. Flip the IO.inspect to a `throw` to enforce.
232-
case neh[:root_chain] do
233-
nil -> :ok
234-
theirs ->
235-
mmr = DB.MMR.load_or_empty()
236-
if mmr.size == neh.height do
237-
ours = MMR.root_chain(DB.MMR.chain_id(), mmr)
238-
if theirs != ours do
239-
IO.inspect(
240-
{:root_chain_mismatch, neh.height,
241-
ours: Base.encode16(ours, case: :lower),
242-
theirs: Base.encode16(theirs, case: :lower)}
243-
)
244-
# throw(%{error: :root_chain_mismatch})
245-
end
231+
if neh.height >= root_chain_height() do
232+
mmr = DB.MMR.load_or_empty()
233+
if mmr.size == neh.height do
234+
case check_root_chain(neh, mmr) do
235+
:ok -> :ok
236+
{:mismatch, ours, theirs} ->
237+
throw(%{error: :root_chain_mismatch, height: neh.height, ours: Base58.encode(ours), theirs: theirs && Base58.encode(theirs)})
246238
end
239+
end
247240
end
248241

249242
chain_epoch = div(neh.height, 100_000)
@@ -278,20 +271,30 @@ defmodule Entry do
278271
validators = DB.Chain.validators_for_height(next_height)
279272
validators_last_change_height = DB.Chain.validators_last_change_height(next_height)
280273

281-
%{
282-
header: %{
283-
slot: cur_entry.header.slot + 1,
284-
height: next_height,
285-
prev_slot: cur_entry.header.slot,
286-
prev_hash: cur_entry.hash,
287-
dr: dr,
288-
vr: vr,
289-
signer: pk,
290-
root_tx: root_tx(Enum.map(txus, & &1.hash)),
291-
root_validator: root_validator(validators, validators_last_change_height)
292-
},
293-
txs: txus
274+
header = %{
275+
slot: cur_entry.header.slot + 1,
276+
height: next_height,
277+
prev_slot: cur_entry.header.slot,
278+
prev_hash: cur_entry.hash,
279+
dr: dr,
280+
vr: vr,
281+
signer: pk,
282+
root_tx: root_tx(Enum.map(txus, & &1.hash), next_height),
283+
root_validator: root_validator(validators, validators_last_change_height, next_height)
294284
}
285+
286+
header =
287+
if next_height >= root_chain_height() do
288+
mmr = DB.MMR.load_or_empty()
289+
if mmr.size != next_height do
290+
raise "build_next root_chain: MMR size #{mmr.size} != next_height #{next_height}; refusing to build (out-of-sync root_chain would halt consensus)"
291+
end
292+
Map.put(header, :root_chain, MMR.root_chain(DB.MMR.chain_id(), mmr))
293+
else
294+
header
295+
end
296+
297+
%{header: header, txs: txus}
295298
end
296299

297300
def sign(seed, entry) do
@@ -313,8 +316,12 @@ defmodule Entry do
313316
entry.header.height
314317
end
315318

316-
def root_tx(hashes) do
317-
RDB.bintree_root(root_tx_build(hashes))
319+
defp merkle_root(kvs, height) do
320+
if height >= root_chain_height(), do: RDB.hbsmt_root(kvs), else: RDB.bintree_root(kvs)
321+
end
322+
323+
def root_tx(hashes, height) do
324+
merkle_root(root_tx_build(hashes), height)
318325
end
319326
def root_tx_build(hashes) do
320327
by_index_hash = Enum.flat_map(Enum.with_index(hashes), fn{hash, index}->
@@ -323,8 +330,8 @@ defmodule Entry do
323330
by_index_hash ++ [{nil, "count", "#{length(hashes)}"}]
324331
end
325332

326-
def root_validator(validator_pks, last_change_height) do
327-
RDB.bintree_root(root_validator_build(validator_pks, last_change_height))
333+
def root_validator(validator_pks, last_change_height, height) do
334+
merkle_root(root_validator_build(validator_pks, last_change_height), height)
328335
end
329336
def root_validator_build(validator_pks, last_change_height) do
330337
by_index_hash = Enum.flat_map(Enum.with_index(validator_pks), fn{hash, index}->

ex/lib/native/rdb.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ defmodule RDB do
5656
def compute_upow(_epoch, _segment_vr_hash, _trainer, _pop, _computor, _diff_bits, _iterations, _threads), do: :erlang.nif_error(:nif_not_loaded)
5757

5858
def bintree_root(_propslist), do: :erlang.nif_error(:nif_not_loaded)
59+
def hbsmt_root(_propslist), do: :erlang.nif_error(:nif_not_loaded)
5960
def bintree_root_prove(_propslist, _ns, _key), do: :erlang.nif_error(:nif_not_loaded)
6061
def bintree_root_verify(_expected_root, _proof, _ns, _key, _value), do: :erlang.nif_error(:nif_not_loaded)
6162
def bintree_contractstate_root_prove(_db, _ns, _key), do: :erlang.nif_error(:nif_not_loaded)

ex/native/rdb/src/consensus/bic/protocol.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::consensus::bic::coin;
22
use crate::consensus::consensus_kv;
33

4-
pub const FORKHEIGHT: u64 = 490_00000;
4+
pub const FORKHEIGHT: u64 = 710_00000;
55
pub const FORKHEIGHT_TESTNET: u64 = 0;
66

77
pub fn forkheight(env: &crate::consensus::consensus_apply::ApplyEnv) -> u64 {
@@ -31,33 +31,23 @@ pub const COST_PER_DB_WRITE_BASE: i128 = 25_000 * 10;
3131
pub const COST_PER_DB_WRITE_BYTE: i128 = 250;
3232

3333
pub fn cost_db_read_byte(env: &crate::consensus::consensus_apply::ApplyEnv) -> i128 {
34-
if env.caller_env.entry_height >= forkheight(env) {
35-
COST_PER_DB_READ_BYTE
36-
} else {
37-
COST_PER_DB_READ_BYTE
38-
};
3934
COST_PER_DB_READ_BYTE
4035
}
4136

4237
pub fn cost_db_write_byte(env: &crate::consensus::consensus_apply::ApplyEnv) -> i128 {
43-
if env.caller_env.entry_height >= forkheight(env) {
44-
COST_PER_DB_WRITE_BYTE
45-
} else {
46-
COST_PER_DB_WRITE_BYTE
47-
};
4838
COST_PER_DB_WRITE_BYTE
4939
}
5040

5141
pub const COST_PER_CALL: i128 = AMA_01_CENT;
5242
pub const COST_PER_DEPLOY: i128 = AMA_1_CENT; //cost to deploy contract
5343
pub const COST_PER_SOL: i128 = AMA_1_CENT; //cost to submit_sol
44+
pub const COST_PER_SLASH: i128 = AMA_1_CENT; //cost to slash_trainer (BLS aggregation over the validator set)
5445
pub const COST_PER_NEW_LEAF_MERKLE: i128 = COST_PER_BYTE_STATE * 128; //cost to grow the merkle tree
5546

5647
pub const LOG_MSG_SIZE: usize = 4096; //max log line length
5748
pub const LOG_TOTAL_SIZE: usize = 16384; //max log total size
5849
pub const LOG_TOTAL_ELEMENTS: usize = 32; //max elements in list
5950
pub const WASM_MAX_PTR_LEN: usize = 1048576; //largest term passable from inside WASM to HOST
60-
//pub const WASM_MAX_PTR_LEN: usize = 32768; //dont smash passed first page
6151
pub const WASM_MAX_PANIC_MSG_SIZE: usize = 128;
6252

6353
pub const MAX_DB_KEY_SIZE: usize = 512;

ex/native/rdb/src/consensus/bic/wasm.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ fn import_call_implementation(mut env: FunctionEnvMut<HostEnv>, table_ptr: i32,
301301
crate::consensus::consensus_kv::exec_budget_decr(applyenv, protocol::COST_PER_CALL);
302302
set_remaining_points(&mut store, &instance, applyenv.exec_left.max(0) as u64);
303303

304+
if applyenv.call_depth >= protocol::MAX_CALL_DEPTH {
305+
panic_any("exec_call_depth_exceeded");
306+
}
307+
304308
let og_account_caller = applyenv.caller_env.account_caller.clone();
305309
let og_account_current = applyenv.caller_env.account_current.clone();
306310

@@ -577,29 +581,40 @@ fn as_read_string(view: &MemoryView, ptr: i32) -> String {
577581
String::from_utf16_lossy(&u16_vec)
578582
}
579583

584+
fn as_peek_len(view: &MemoryView, ptr: i32) -> usize {
585+
let len_ptr = match (ptr as u64).checked_sub(4) {
586+
Some(p) => p,
587+
None => return 0,
588+
};
589+
let mut len_buf = [0u8; 4];
590+
if view.read(len_ptr, &mut len_buf).is_err() {
591+
return 0;
592+
}
593+
(u32::from_le_bytes(len_buf) as usize).min(protocol::WASM_MAX_PTR_LEN)
594+
}
595+
580596
fn as_abort_implementation(mut env: FunctionEnvMut<HostEnv>, msg_ptr: i32, filename_ptr: i32, line: i32, column: i32) -> Result<(), RuntimeError> {
581597
let (data, mut store) = env.data_and_store_mut();
582598
let instance = data.instance.clone().unwrap_or_else(|| panic_any("exec_instance_not_injected"));
583599
let applyenv = unsafe { data.applyenv_ptr.as_mut() };
584600
budget_sync_in(&mut store, &instance, applyenv);
585601
let view = data.memory.clone().view(&store);
586602

587-
//set_return_value(applyenv, b"as_abort".to_vec());
588-
603+
crate::consensus::consensus_kv::exec_budget_decr(applyenv, protocol::cost_per_bytes_historical(as_peek_len(&view, msg_ptr)));
589604
let msg = as_read_string(&view, msg_ptr);
590-
let filename = as_read_string(&view, filename_ptr);
591605

592-
let full_error_msg = format!("as_abort: '{}' at {}:{}:{}", msg, filename, line, column);
606+
crate::consensus::consensus_kv::exec_budget_decr(applyenv, protocol::cost_per_bytes_historical(as_peek_len(&view, filename_ptr)));
607+
let filename = as_read_string(&view, filename_ptr);
593608

594-
crate::consensus::consensus_kv::exec_budget_decr(applyenv, protocol::cost_per_bytes_historical(full_error_msg.len()));
609+
// Sync the meter only after `view`'s last use (set_remaining_points needs &mut store).
595610
set_remaining_points(&mut store, &instance, applyenv.exec_left.max(0) as u64);
596611

612+
let full_error_msg = format!("as_abort: '{}' at {}:{}:{}", msg, filename, line, column);
597613
log_line(applyenv, full_error_msg.as_bytes().to_vec());
598614

599615
//TODO: is this OK?
600616
panic_any("as_abort");
601617
Ok(())
602-
//Err(RuntimeError::new("as_abort"))
603618
}
604619

605620
fn as_seed_implementation(mut env: FunctionEnvMut<HostEnv>) -> Result<f64, RuntimeError> {
@@ -622,7 +637,7 @@ fn log_line(applyenv: &mut ApplyEnv, line: Vec<u8>) {
622637
if (applyenv.logs_size.saturating_add(len)) > protocol::LOG_TOTAL_SIZE {
623638
panic_any("exec_logs_total_size_exceeded")
624639
}
625-
if applyenv.logs.len() > protocol::LOG_TOTAL_ELEMENTS {
640+
if applyenv.logs.len() >= protocol::LOG_TOTAL_ELEMENTS {
626641
panic_any("exec_logs_total_elements_exceeded")
627642
}
628643

0 commit comments

Comments
 (0)