Change debug_node_plugin behavior to more intuitive.
Unit tests in general have full access to chain state, both for reading and writing. However, in case test code modifies state, changes only persist until next call to generate_block
or similar routine, since first thing that happens then is to undo
pending changes, then reapply them using list of pending transactions (in fact it is exactly like when node runs normally: push_transaction
from the test code apply changes to state immediately, like normal transactions incoming from API calls or P2P, and put transaction in pending list and when block is generated it unwinds pending changes, produces block out of pending transactions and then tries to reapply them all again - they will be dropped from pending list as "known", so it only matters if we have too many pending to fit in block, which doesn't typically happen during tests). The "artificial chain modifications" are not transactions, so they are not included when block is produced, which means they are lost.
This is where debug_node_plugin
's debug_update
comes into play. When artificial state changes from test are wrapped in such call, they persist through block production. The way it is implemented leads to some very strange side effects, which might result in unit test developer wasting lots of time trying to figure out why the test does not work.
debug_update
remembers given lambda binding it to head_block_id()
, then pops block (which removes effects of all pending transactions) and immediately pushes it. The lambda is executed inside on_post_apply_block
signal within apply_debug_updates
call. Only then, during next generate_block
call the popped transactions are reapplied. There are three associated problems:
- since lambda is bound to block id, there can be only one per block, the latter silently overwrites the former
- order of transactions and debug updates from test is disturbed
- when lambda code fails, the resulting exception is caught inside
HIVE_TRY_NOTIFY
and it only shows in the log, but the test continues, unlike other exceptions
Simple real life example:
ACTORS( (alice)(bob) )
fund( "alice", "10.000 TESTS" );
The ACTORS
macro creates given accounts, the fund
call uses debug_update
to wrap issuance of new currency that is then placed on target balance. If the code had generate_block
immediately following ACTORS
it would work, if it used fund( "alice", 10000 );
it would also work (because for unknown reason there are two fundamentally different fund
routines, and the latter uses regular transfer_operation
from init_miner
instead of issuing new currency). But like it is presented it does not work, because debug_update
hidden inside fund
call is undo
ing account creations, which leads to alice
not being present during funding, leading to exception that only shows in the log and the test continues normally, in particular it reapplies account creations later. So in the end we have given accounts but no funding.
To address first two problems associated with debug_update
, I propose we replace current weird implementation with binding lambda to transaction id instead of block id. Transaction would be generated during call (it has to contain something, but it can be f.e. empty custom_operation
signed by init_miner
) and pushed to pending like any other transactions. We'd replace handling inside on_post_apply_block
with on_post_apply_transaction
. Since order of pending transactions is the order in which they were pushed, when they are reapplied during generate_block
call, natural order from the test is preserved. Also there can be more than one debug_update
per block.
Alternative solution would be to introduce custom evaluator for new custom_json_operation
(see how delegate_rc_operation
is defined and handled as a model). Such solution would also address third problem, because it would no longer need to be handled inside signal, but inside evaluator, which passes all exceptions to be then caught inside the test code naturally (when it is_producing
, which is the case here). It is not strictly necessary though, because the third problem can also be addressed by catching lambda exceptions inside apply_debug_updates
and translating them to plugin_exception
which is passed through HIVE_TRY_NOTIFY
.
In the end the following test scenario should work:
- remember
head_block_num()
- create
alice
- issue funds to her (
debug_update
) - change required creation fee (
debug_update
) - create
bob
with new fee - transfer funds from
alice
tobob
- issue some more funds to
bob
(debug_update
) - only now generate block
- check if the block is the next to the one remembered to prevent
generate_block
call being hidden in previous calls - check the balances on
alice
andbob
The test usesdebug_update
interweaved with regular transactions in such a way that disturbed order ordebug_update
lambdas overwriting themselves would break the test.