This changes the credit rules for recommission: instead of being
restored with no credit at all, with this you get restored with 2 blocks
of credit lost (up to all of your credit) for each block of decommission
time you had.
For example, if you had 1000 blocks of credit and are decommissioned for
150 blocks, you'll get recommissioned with 700 blocks of credit. If you
were decommissioned for 500 or more blocks then you'll get
decommissioned with 0 blocks of credit.
This applies the effects immediately rather than waiting for the hard
fork. Because this isn't a consensus-breaking change it seems easier to
not worry about the HF rules. The consequences are that the rules that
get applied to determining how much credit you have will depend on the
nodes involved in testing up until the hard fork: you *may* be subject
to the harsher v7 rules, or you may fall under v8 rules if enough of the
testing quorum has upgraded, but you'll never end up worse than the v7
rules.
This prevents staking transactions from being accepted if they overstake
the available contribution room by more than 1%. This is to prevent a
case that has happened a few times where there are competing partial
stakes submitted for the same SN at the same time (i.e. before a block
gets mined with the stakes). For example:
- Operator registers service node with 30% contribution
- Staker 1 submits stake with 40% contribution
- Staker 2 submits stake with 60% contribution
The wallet avoids stake 2 if the 40% has been accepted into a block, but
doesn't if it is still in the mempool. Later, when the contributions
get mined, both stakes are admitted because whichever one goes first
doesn't complete the stake, and the second one is still valid (since
there is a spot, and since it contributes >= the required amount).
Whichever stake gets added to a block second, however, will only be
counted as a contribution of the available amount. So, for example, if
stake 1 gets added first and then stake 2 gets added you'll end up with
an active service node of:
- operator has 30% contributed and locked
- staker 1 has 40% contributed and locked
- staker 2 has 30% contributed but 60% locked.
This commit adds an upper bound for an acceptable stake that is 101% of
the available contribution room so that, in the above situation,
whichever stake gets added first will be a contribution and the second
one will fall through as an ordinary transaction back to the staker's
wallet so that the staker the re-contribute the proper amount.
- Fix assert to use version_t::_count in service_node_list.cpp
- Incoporate staking changes from LNS which revamp construct tx to
derive more of the parameters from hf_version and type. This removes
extraneous parameters that can be derived elsewhere.
Also delegate setting loki_construct_tx_params into wallet2 atleast,
so that callers into the wallet logic, like rpc server, simplewallet
don't need bend backwards to get the HF version whereas the wallet has
dedicated functions for determining the HF.
This adds a thread-local, pre-seeded rng at `tools::rng` (to avoid the
multiple places we are creating + seeding such an RNG currently).
This also moves the portable uniform value and generic shuffle code
there as well as neither function is specific to service nodes and this
seems a logical place for them.
This is the bulk of the work for blink. There is two pieces yet to come
which will follow shortly, which are: the p2p communication of blink
transactions (which needs to be fully synchronized, not just shared,
unlike regular mempool txes); and an implementation of fee burning.
Blink approval, multi-quorum signing, cli wallet and node support for
submission denial are all implemented here.
This overhauls and fixes various parts of the SNNetwork interface to fix
some issues (particularly around non-SN communication with SNs, which
wasn't working).
There are also a few sundry FIXME's and TODO's of other minor details
that will follow shortly under cleanup/testing/etc.
Unify the field we use to store the count as `_count` (using the leading
underscore to indicate a private value rather than an intended enum
value) and add/use a new `enum_count` template variable to extract the
_count enum value and cast it to the enum's underlying_type.
This adds vote relaying via quorumnet.
- The main glue between existing code and quorumnet code is in
cryptonote_protocol/quorumnet.{h,cpp}
- Uses function pointers assigned at initialization to call into the
glue without making cn_core depend on p2p
- Adds ed25519 and quorumnet port to uptime proofs and serialization.
- Added a second uptime proof signature using the ed25519 key to that
SNs are proving they actually have it (otherwise they could spoof
someone else's pubkey).
- Adds quorumnet port, defaulting to zmq rpc port + 1. quorumnet
listens on the p2p IPv4 address (but broadcasts the `public_ip` to the
network).
- Derives x25519 when received or deserialized.
- Converted service_info_info::version_t into a scoped enum (with the
same underlying type).
- Added contribution_t::version_t scoped enum. It was using
service_info_info::version for a 0 value which seemed rather wrong.
Random small details:
- Renamed internal `arg_sn_bind_port` for consistency
- Moved default stagenet ports from 38153-38155 to 38056-38058, and add the default stagenet
quorumnet port at 38059 (this keeps the range contiguous; otherwise the next stagenet default port
would collide with the testnet p2p port).
You'll now hit this maxmimum if you've been up for ~60d. Note that this
will not fully apply until the whole network has upgraded to v5; before
the fork height non-upgraded SNs voting for you will still only award
max 24h credit.
- Replace a bunch of repetitive proof rejection rules with a macro +
HF requirement version loop.
- Remove mostly unused MAX_KEY_IMAGES_PER_CONTRIBUTOR variable. It has
always been set to 1 (and so doesn't actually do anything). If we
wanted to increase it at this point we'd have to add multiple
HF-guarded versions of it and HF-version guard the code anyway, so
seems simpler to just drop it.
- Simplify the max contributor/max contribution logic and merge it in
with the existing contributor code (this is made easier by the above
dropped variable).
* Enforce minimum storage server version since hardfork 13
* Send Storage Server version as a triplet
* Send Storage Server version as a triplet of numbers, not strings
* Enforce Storage Server version immediately
If we are re-deriving states we still also need the historical quorums
such that we can process incoming blocks with state changes. Without it,
state changes will be ignored and skipped causing inconsistent state in
the service node list.
This mandates a rescan and stores the most recent in state_ts, and only
just quorums for states preceeding 10k intervals. We can no longer store
the 1st most recent state in the DB as that will be missing quorums as
well for similar reason when rederiving on start-up.
* Store the oldest state in the MAX_SHORT_TERM_STATE_HISTORY window
Rederive the rest on startup, this way we only need to store states for
10k intervals + 1 state every block temporarily removing the overhead of
having to serialise 30+(every 10k interval) states every block which was
taking upto 15ms~ per block on mainnet.
* Store quorums into the state class
This is setup work to allow only storing one service node list state and
rederiving the rest of the state at runtime in memory without having to
waste space storing the extra 360 quorums every block which is the
biggest bottleneck with syncing the blockchain adding around ~15ms per
block after Service Nodes are activated.
* Derive quorums using correct state, record duration of scan
* Don't fail load if quorums empty, save progress on rescan
Storing quorums in quorum states is now optional because quorums are
stored with the state objects. Quorums are only stored in the dedicated
data structure if you requested to store quorum history so for most
users this will be empty.
* Store state upto quorum lifetime to match previous behaviour
* Make store every 10k states more robust
* Corectly use lower bound to find quorum
* Code review
* Don't cull checkpoints if they bleed into the finality zone
If we miss many checkpoints for whatever reason, we could accidentally
delete checkpoints due to the cull height not checking what checkpoints
we have available
* Naming convention of const, keep the 3rd last checkpoint
* Should not be +1 for checkpoint finality
* Rebase and use get_immutable_checkpoint_height()
Increase the range to mitigate frequent rescanning when reorganising,
technically with checkpointing we only need up to the 2nd oldest
checkpoint, but I'm sure there's some case we're forgetting that would
benefit from a larger rollback range.
Also considering there were only 160 events in the month with a mass
deregistration- keeping 2 days worth of rollbacks around is not going to
be too expensive.
Start storing service node list state for past blocks
Separate quorums and reduce storage to 1 days worth
Don't use pointer, declare m_state by value
Fix rebase to dev
Fix double serialise of service node state and expensive blob copy
std::move quorums into storage, reserve vector size
Don't +1 checkpoint finality height
Code review
Code review 2
* Add soft forking for checkpointing on mainnet
* Clear checkpoints on softfork on mainnet
* Move softfork date until we're ready, fix test results and modulo addition
* Only round up delete_height if it's not a multiple of CHECKPOINT_INTERVAL
* Remove unused variable soft fork in service_node_rules (replaced by cryptonote_config)
* Use quorums N-MAX_REORG_BLOCKS_POST_HF12 for checkpointing
Nodes on an alternative chain will generate quorums reflecting those on
the alternate chain. Since checkpointing prevents reorgs up to
MAX_REORG_BLOCKS_POST_HF12, use quorums exactly that many blocks old to
ensure that at any point, all nodes will be working in quorums that are
consistent between each other.
* Fix round-up to nearest checkpoint interval, update misleading MAX_REORG var name
* Don't repeatedly round up the value to CHECKPOINT_INTERVAL if already rounded
* Remove redundant modulo line for m_last_checkpointed_height
* Restore large reorg message since checkpointing doesn't guarantee the reorg limit
* Add deregistration of checkpoints by checking how many votes are missed
Move uptime proofs and add checkpoint counts in the service_node_list
because we typically prune uptime proofs by time, but it seems we want
to switch to a model where we persist proof data until the node expires
otherwise currently we would prune uptime entries and potentially our
checkpoint vote counts which would cause premature deregistration as the
expected vote counts start mismatching with the number of received
votes.
* Revise deregistration
* Fix test breakages
* uint16_t for port, remove debug false, min votes to 2 in integration mode
* Fix integration build
This adds a new obligations quorum vote "ip_change_penalty" that gets
triggered if the quorum has received multiple IPs advertised in uptime
proofs from a service node in the past 24 hours. Upon reception of such
a transaction the SN gets bumped to the bottom of the reward list.
* core: do not commit half constructed batch db txn
* Add defer macro
* Revert dumb extra copy/move change
* Fix pop_blocks not calling hooks, fix BaseTestDB missing prototypes
* Merge ServiceNodeCheckpointing5 branch, syncing and integration fixes
* Update tests to compile with relaxed-registration changes
* Get back to feature parity pre-relaxed registration changes
* Remove debug changes noticed in code review and some small bugs
This starts everyone with 2 hours of credit at deregistration (instead
of none), and reduces the minimum required for decommissioning to 2
hours so that everyone can get some decommission right away.
Otherwise the credit accumulation rate and maximum remain unchanged: 24
blocks per day = 24 hours worth of blocks per 30 days, and a maximum
accumulated credit of 24 hours (which you'll now hit 27.5 days after
registration instead of 30 because of the initial 2 hour credit).
The replaces the deregistration mechanism with a new state change
mechanism (beginning at the v12 fork) which can change a service node's
network status via three potential values (and is extensible in the
future to handle more):
- deregistered -- this is the same as the existing deregistration; the
SN is instantly removed from the SN list.
- decommissioned -- this is a sort of temporary deregistration: your SN
remains in the service node list, but is removed from the rewards list
and from any network duties.
- recommissioned -- this tx is sent by a quorum if they observe a
decommissioned SN sending uptime proofs again. Upon reception, the SN
is reactivated and put on the end of the reward list.
Since this is broadening the quorum use, this also renames the relevant
quorum to a "obligations" quorum (since it validates SN obligations),
while the transactions are "state_change" transactions (since they
change the state of a registered SN).
The new parameters added to service_node_rules.h control how this works:
// Service node decommissioning: as service nodes stay up they earn "credits" (measured in blocks)
// towards a future outage. A new service node starts out with INITIAL_CREDIT, and then builds up
// CREDIT_PER_DAY for each day the service node remains active up to a maximum of
// DECOMMISSION_MAX_CREDIT.
//
// If a service node stops sending uptime proofs, a quorum will consider whether the service node
// has built up enough credits (at least MINIMUM): if so, instead of submitting a deregistration,
// it instead submits a decommission. This removes the service node from the list of active
// service nodes both for rewards and for any active network duties. If the service node comes
// back online (i.e. starts sending the required performance proofs again) before the credits run
// out then a quorum will reinstate the service node using a recommission transaction, which adds
// the service node back to the bottom of the service node reward list, and resets its accumulated
// credits to 0. If it does not come back online within the required number of blocks (i.e. the
// accumulated credit at the point of decommissioning) then a quorum will send a permanent
// deregistration transaction to the network, starting a 30-day deregistration count down.
This commit currently includes values (which are not necessarily
finalized):
- 8 hours (240 blocks) of credit required for activation of a
decommission (rather than a deregister)
- 0 initial credits at registration
- a maximum of 24 hours (720 blocks) of credits
- credits accumulate at a rate that you hit 24 hours of credits after 30
days of operation.
Miscellaneous other details of this PR:
- a new TX extra tag is used for the state change (including
deregistrations). The old extra tag has no version or type tag, so
couldn't be reused. The data in the new tag is slightly more
efficiently packed than the old deregistration transaction, so it gets
used for deregistrations (starting at the v12 fork) as well.
- Correct validator/worker selection required generalizing the shuffle
function to be able to shuffle just part of a vector. This lets us
stick any down service nodes at the end of the potential list, then
select validators by only shuffling the part of the index vector that
contains active service indices. Once the validators are selected, the
remainder of the list (this time including decommissioned SN indices) is
shuffled to select quorum workers to check, thus allowing decommisioned
nodes to be randomly included in the nodes to check without being
selected as a validator.
- Swarm recalculation was not quite right: swarms were recalculated on
SN registrations, even if those registrations were include shared node
registrations, but *not* recalculated on stakes. Starting with the
upgrade this behaviour is fixed (swarms aren't actually used currently
and aren't consensus-relevant so recalculating early won't hurt
anything).
- Details on decomm/dereg are added to RPC info and print_sn/print_sn_status
- Slightly improves the % of reward output in the print_sn output by
rounding it to two digits, and reserves space in the output string to
avoid excessive reallocations.
- Adds various debugging at higher debug levels to quorum voting (into
all of voting itself, vote transmission, and vote reception).
- Reset service node list internal data structure version to 0. The SN
list has to be rescanned anyway at upgrade (its size has changed), so we
might as well reset the version and remove the version-dependent
serialization code. (Note that the affected code here is for SN states
in lmdb storage, not for SN-to-SN communication serialization).
* Initial updates to allow syncing of checkpoints in protocol_handler
* Handle checkpoints in prepare_handle_incoming_blocks
* Debug changes for testing, cancel changes later
* Add checkpoint pruning code
* Reduce DB checkpoint accesses, sync votes for up to 60 blocks
* Remove duplicate lifetime variable
* Move parsing checkpoints to above early return for consistency
* Various integration test fixes
- Don't try participate in quorums at startup if you are not a service node
- Add lock for when potentially writing to the DB
- Pre-existing batch can be open whilst updating checkpoint so can't use
DB guards
- Temporarily emit the ban message using msgwriter instead of logs
* Integration mode bug fixes
- Always deserialize empty quorums so get_testing_quorum only fails when
an actual critical error has occurred
- Cache the last culled height so we don't needlessly query the DB over
the same heights trying to delete already-deleted checkpoints
- Submit votes when new blocks arrive for more reliable vote
transportation
* Undo debug changes for testing
* Update incorrect DB message and stale comment
* Unify checkpointing and uptime quorums
* Begin making checkpoints cull old votes/checkpoints
* Begin rehaul of service node code out of core, to assist checkpoints
* Begin overhaul of votes to move resposibility into quorum_cop
* Update testing suite to work with the new system
* Remove vote culling from checkpoints and into voting_pool
* Fix bugs making integration deregistration fail
* Votes don't always specify an index in the validators
* Update tests for validator index member change
* Rename deregister to voting, fix subtle hashing bug
Update the deregister hash derivation to use uint32_t as originally set
not uint64_t otherwise this affects the result and produces different
results.
* Remove un-needed nettype from vote pool
* PR review, use <algorithms>
* Rename uptime_deregister/uptime quorums to just deregister quorums
* Remove unused add_deregister_vote, move side effect out of macro
* Collect checkpoint votes and signatures
* Copy deregister vote and adapt for checkpoint vote
Monkey see, monkey do
Write boilerplate code for receiving and relaying votes
* Make current checkpoints service node aware
Fix not saving checkpoint hash for normal checkpoints
Simplify some of the checkpointing API
* Verify blocks against service node checkpoints
Remove checkpoint copy, make checkpoint at vote threshold
Fix null ptr dereferences, add debug print checkpoints
* Add simple node checkpointing w/no conflict resolution
* Use endl to flush output
* Add hash to vote
* Revise checkpoint rules
* Don't store checkpoints in a linked list
* Commit checkpoints on super majority of votes received
* Add locks since checkpoints is accessed in network thread
* Handle checkpoint vote conflicts better
* Comment out early exiting voting process for simplicity
* Clean up for code review
* Fix whitespacing
* More cleanup for review
* Remove debug changes, fix using upper_bound incorrectly, return points by value
* find_if uses == not < as the predicate
* Fix merge issues from rebasing multiple quorum types
* unit tests and core tests for new queueless swarm assignment logic
* new bufferless swarm assignment logic
* new lines
* Address reviews
* Improve node stealing logic
* unit tests for additional functions
* get_excess_pool to return early if the threshold is lower than MIN_SWARM_SIZE
* Address reviews
* another round of reviews
* Move new_swarm_vector inside loop
* Rebase changes: moved get_new_swarm_id to service_node_swarm.cpp
* reserve vector size for all_ids in get_new_swarm_id
* Cleanup and undoing some protocol breakages
* Simplify expiration of nodes
* Request unlock schedules entire node for expiration
* Fix off by one in expiring nodes
* Undo expiring code for pre v10 nodes
* Fix RPC returning register as unlock height and not checking 0
* Rename key image unlock height const
* Undo testnet hardfork debug changes
* Remove is_type for get_type, fix missing var rename
* Move serialisable data into public namespace
* Serialise tx types properly
* Fix typo in no service node known msg
* Code review
* Fix == to >= on serialising tx type
* Code review 2
* Fix tests and key image unlock
* Add command to print locked key images
* Update ui to display lock stakes, query in print cmd blacklist
* Modify print stakes to be less slow
* Remove autostaking code
* Refactor staking into sweep functions
It appears staking was derived off stake_main written separately at
implementation at the beginning. This merges them back into a common
code path, after removing autostake there's only some minor differences.
It also makes sure that any changes to sweeping upstream are going to be
considered in the staking process which we want.
* Display unlock height for stakes
* Begin creating output blacklist
* Make blacklist output a migration step
* Implement get_output_blacklist for lmdb
* In wallet output selection ignore blacklisted outputs
* Apply blacklisted outputs to output selection
* Fix broken tests, switch key image unlock
* Fix broken unit_tests
* Begin change to limit locked key images to 4 globally
* Revamp prepare registration for new min contribution rules
* Fix up old back case in prepare registration
* Remove debug code
* Cleanup debug code and some unecessary changes
* Fix migration step on mainnet db
* Fix blacklist outputs for pre-existing DB's
* Remove irrelevant note
* Tweak scanning addresses for locked stakes
Since we only now allow contributions from the primary address we can
skip checking all subaddress + lookahead to speed up wallet scanning
* Define macro for SCNu64 for Mingw
* Fix failure on empty DB
* Add missing error msg, remove contributor from stake
* Improve staking messages
* Flush prompt to always display
* Return the msg from stake failure and fix stake parsing error
* Tweak fork rules for smaller bulletproofs
* Tweak pooled nodes minimum amounts
* Fix crash on exit, there's no need to store on destructor
Since all information about service nodes is derived from the blockchain
and we store state every time we receive a block, storing in the
destructor is redundant as there is no new information to store.
* Make prompt be consistent with CLI
* Check max number of key images from per user to node
* Implement error message on get_output_blacklist failure
* Remove resolved TODO's/comments
* Handle infinite staking in print_sn
* Atoi->strtol, fix prepare_registration, virtual override, stale msgs
* Remove dead branches in hot-path check_tx_inputs
Also renames #define for mixins to better match naming convention
* Shuffle around some more code into common branches
* Fix min/max tx version rules, since there 1 tx v2 on v9 fork
* First draft infinite staking implementation
* Actually generate the right key image and expire appropriately
* Add framework to lock key images after expiry
* Return locked key images for nodes, add request unlock option
* Introduce transaction types for key image unlock
* Update validation steps to accept tx types, key_image_unlock
* Add mapping for lockable key images to amounts
* Change inconsistent naming scheme of contributors
* Create key image unlock transaction type and process it
* Update tx params to allow v4 types and as a result construct_tx*
* Fix some serialisation issues not sending all the information
* Fix dupe tx extra tag causing incorrect deserialisation
* Add warning comments
* Fix key image unlocks parsing error
* Simplify key image proof checks
* Fix rebase errors
* Correctly calculate the key image unlock times
* Blacklist key image on deregistration
* Serialise key image blacklist
* Rollback blacklisted key images
* Fix expiry logic error
* Disallow requesting stake unlock if already unlocked client side
* Add double spend checks for key image unlocks
* Rename get_staking_requirement_lock_blocks
To staking_initial_num_lock_blocks
* Begin modifying output selection to not use locked outputs
* Modify output selection to avoid locked/blacklisted key images
* Cleanup and undoing some protocol breakages
* Simplify expiration of nodes
* Request unlock schedules entire node for expiration
* Fix off by one in expiring nodes
* Undo expiring code for pre v10 nodes
* Fix RPC returning register as unlock height and not checking 0
* Rename key image unlock height const
* Undo testnet hardfork debug changes
* Remove is_type for get_type, fix missing var rename
* Move serialisable data into public namespace
* Serialise tx types properly
* Fix typo in no service node known msg
* Code review
* Fix == to >= on serialising tx type
* Code review 2
* Fix tests and key image unlock
* Add additional test, fix assert
* Remove debug code in wallet
* Fix merge dev problem
* Allow `prepare_registration` to work on command-line
This allows you to use `lokid prepare_registration` to generate a
registration command via RPC commands to the lokid, which is
particularly useful when the lokid is running non-interactively as a
system service.
Without this it is necessary to stop the lokid service, start it
manually, run the prepare_registration, then stop lokid and restart it
via the system service.
This also rewrites the description of the prepare_registration command to
remove the reference to saving to disk.
* make getting registration command more user-friendly via rpc
* better error message for get registration command rpc
* move get_service_node_registration_cmd into prepare_registration
* show error message on unsuccessful registration in simplewallet as well
* Enable staking in GUI wallet
* Move more helper functions into service_node_rules.h
* Move validation of arguments for staking into wallet2
* call stake validation from within create_stake_tx