Implement genesis reconciliation protocol for sidechain stability

Problem: P2Pool nodes starting at different times or experiencing network
issues would create independent genesis blocks, resulting in incompatible
chains. Nodes would ban each other for invalid blocks that were actually
valid on a different chain. Cache resume after restart frequently failed
due to genesis mismatch between nodes.

Solution:  Oldest compatible genesis wins protocol that coordinates
genesis selection across peers before mining begins.

New P2P message GENESIS_INFO exchanges:
- Genesis block hash
- Genesis timestamp
- Genesis mainchain height
- Protocol version

Startup behavior:
- Wait up to 90 seconds for peer genesis info (with progress logging)
- Adopt oldest genesis from compatible peers
- Only create own genesis if no peers respond

Late joiner reconciliation:
- Running nodes that receive older genesis from new peer will purge
  their sidechain and re-sync to the older chain
- Cache files deleted on purge to prevent reload of stale blocks

Protocol versioning:
- PROTOCOL_VERSION constant at top of side_chain.h
- Increment only on consensus-breaking changes
- Version mismatch logs warning, prevents genesis adoption

Tiebreaker: When timestamps match, lexicographically lower hash wins.
This commit is contained in:
Matt Hess
2025-12-05 23:44:53 +00:00
parent a5ee896215
commit 00fb078004
18 changed files with 1011 additions and 279 deletions
+43
View File
@@ -21,6 +21,7 @@
#include "common.h"
#include "wallet.h"
#include "keccak.h"
#include "carrot_crypto.h"
#include "crypto.h"
#include <ios>
@@ -377,4 +378,46 @@ bool Wallet::torsion_check() const
fcmp_pp::torsion_check_vartime(p2);
}
bool Wallet::get_eph_public_key_carrot(const hash& tx_key_seed, uint64_t height, size_t output_index, uint64_t amount, hash& eph_public_key, uint8_t& view_tag) const
{
(void)output_index; // Not used - anchor derived from spend pubkey, not position
// 1. Build input_context from height
uint8_t input_context[33];
carrot::make_input_context_coinbase(height, input_context);
// 2. Derive anchor from wallet's spend public key (position-independent)
uint8_t anchor[16];
carrot::derive_deterministic_anchor_from_pubkey(tx_key_seed, m_spendPublicKey, anchor);
// 3. Derive per-output ephemeral private key d_e from anchor
static const uint8_t null_payment_id[8] = {0};
hash ephemeral_privkey;
carrot::make_ephemeral_privkey(anchor, input_context, m_spendPublicKey, null_payment_id, ephemeral_privkey);
// 4. Derive ephemeral public key D_e = d_e * B
hash ephemeral_pubkey;
carrot::make_ephemeral_pubkey_mainaddress(ephemeral_privkey, ephemeral_pubkey);
// 5. Derive shared secret using this wallet's view pubkey
hash shared_secret;
if (!carrot::make_shared_secret_sender(ephemeral_privkey, m_viewPublicKey, shared_secret)) {
return false;
}
// 6. Derive sender-receiver secret
hash sender_receiver_secret;
carrot::make_sender_receiver_secret(shared_secret, ephemeral_pubkey, input_context, sender_receiver_secret);
// 7. Derive onetime address K_o
carrot::make_onetime_address_coinbase(m_spendPublicKey, sender_receiver_secret, amount, eph_public_key);
// 8. Derive view tag
uint8_t view_tag_full[3];
carrot::make_view_tag(shared_secret, input_context, eph_public_key, view_tag_full);
view_tag = view_tag_full[0];
return true;
}
} // namespace p2pool