chia-blockchain/src/farmer/farmer_api.py

267 lines
12 KiB
Python
Raw Normal View History

2020-12-11 11:02:09 +03:00
import time
2020-12-01 11:22:58 +03:00
from typing import Callable
2020-10-16 04:03:46 +03:00
from blspy import AugSchemeMPL, G2Element
import src.server.ws_connection as ws
2020-10-16 04:03:46 +03:00
2020-12-11 10:27:03 +03:00
from src.consensus.pot_iterations import (
calculate_iterations_quality,
calculate_sp_interval_iters,
)
2020-12-10 19:34:56 +03:00
from src.farmer.farmer import Farmer
2020-10-16 04:03:46 +03:00
from src.protocols import harvester_protocol, farmer_protocol
from src.server.outbound_message import Message, NodeType
from src.types.pool_target import PoolTarget
from src.types.proof_of_space import ProofOfSpace
from src.util.api_decorators import api_request, peer_required
2020-12-01 11:22:58 +03:00
from src.util.ints import uint32, uint64
2020-10-16 04:03:46 +03:00
class FarmerAPI:
farmer: Farmer
def __init__(self, farmer):
self.farmer = farmer
2020-10-21 11:19:40 +03:00
def _set_state_changed_callback(self, callback: Callable):
self.farmer.state_changed_callback = callback
2020-10-16 04:03:46 +03:00
@api_request
@peer_required
async def new_proof_of_space(
self, new_proof_of_space: harvester_protocol.NewProofOfSpace, peer: ws.WSChiaConnection
):
2020-10-16 04:03:46 +03:00
"""
This is a response from the harvester, for a NewChallenge. Here we check if the proof
of space is sufficiently good, and if so, we ask for the whole proof.
"""
2020-12-03 16:49:14 +03:00
if new_proof_of_space.sp_hash not in self.farmer.number_of_responses:
self.farmer.number_of_responses[new_proof_of_space.sp_hash] = 0
2020-12-11 11:02:09 +03:00
self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time()))
2020-10-16 04:03:46 +03:00
2020-12-05 17:28:41 +03:00
max_pos_per_sp = 5
if self.farmer.number_of_responses[new_proof_of_space.sp_hash] > max_pos_per_sp:
2020-11-26 09:22:13 +03:00
self.farmer.log.warning(
f"Surpassed {max_pos_per_sp} PoSpace for one SP, no longer submitting PoSpace for signage point "
2020-12-03 16:49:14 +03:00
f"{new_proof_of_space.sp_hash}"
2020-10-16 04:03:46 +03:00
)
2020-11-26 09:22:13 +03:00
return
2020-10-16 04:03:46 +03:00
2020-12-03 16:49:14 +03:00
if new_proof_of_space.sp_hash not in self.farmer.sps:
2020-11-26 09:22:13 +03:00
self.farmer.log.warning(
2020-12-03 16:49:14 +03:00
f"Received response for a signage point that we do not have {new_proof_of_space.sp_hash}"
2020-11-26 09:22:13 +03:00
)
return
2020-10-16 04:03:46 +03:00
sps = self.farmer.sps[new_proof_of_space.sp_hash]
for sp in sps:
computed_quality_string = new_proof_of_space.proof.verify_and_get_quality_string(
2020-12-11 10:27:03 +03:00
self.farmer.constants,
new_proof_of_space.challenge_hash,
new_proof_of_space.sp_hash,
)
if computed_quality_string is None:
self.farmer.log.error(f"Invalid proof of space {new_proof_of_space.proof}")
return
2020-10-16 04:03:46 +03:00
self.farmer.number_of_responses[new_proof_of_space.sp_hash] += 1
2020-11-26 09:22:13 +03:00
required_iters: uint64 = calculate_iterations_quality(
computed_quality_string,
new_proof_of_space.proof.size,
sp.difficulty,
new_proof_of_space.sp_hash,
)
# Double check that the iters are good
assert required_iters < calculate_sp_interval_iters(self.farmer.constants, sp.sub_slot_iters)
2020-11-26 09:22:13 +03:00
# Proceed at getting the signatures for this PoSpace
request = harvester_protocol.RequestSignatures(
new_proof_of_space.plot_identifier,
new_proof_of_space.challenge_hash,
new_proof_of_space.sp_hash,
[sp.challenge_chain_sp, sp.reward_chain_sp],
)
2020-11-26 09:22:13 +03:00
if new_proof_of_space.sp_hash not in self.farmer.proofs_of_space:
self.farmer.proofs_of_space[new_proof_of_space.sp_hash] = [
(
new_proof_of_space.plot_identifier,
new_proof_of_space.proof,
)
]
else:
self.farmer.proofs_of_space[new_proof_of_space.sp_hash].append(
(
new_proof_of_space.plot_identifier,
new_proof_of_space.proof,
)
2020-10-16 04:03:46 +03:00
)
2020-12-11 11:02:09 +03:00
self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time()))
self.farmer.quality_str_to_identifiers[computed_quality_string] = (
new_proof_of_space.plot_identifier,
new_proof_of_space.challenge_hash,
new_proof_of_space.sp_hash,
peer.peer_node_id,
2020-10-16 04:03:46 +03:00
)
2020-12-11 11:02:09 +03:00
self.farmer.cache_add_time[computed_quality_string] = uint64(int(time.time()))
2020-10-16 04:03:46 +03:00
return Message("request_signatures", request)
2020-10-16 04:03:46 +03:00
@api_request
2020-11-26 09:22:13 +03:00
async def respond_signatures(self, response: harvester_protocol.RespondSignatures):
2020-10-16 04:03:46 +03:00
"""
2020-11-26 09:22:13 +03:00
There are two cases: receiving signatures for sps, or receiving signatures for the block.
2020-10-16 04:03:46 +03:00
"""
2020-11-26 09:22:13 +03:00
if response.sp_hash not in self.farmer.sps:
self.farmer.log.warning(f"Do not have challenge hash {response.challenge_hash}")
return
is_sp_signatures: bool = False
sps = self.farmer.sps[response.sp_hash]
signage_point_index = sps[0].signage_point_index
found_sp_hash_debug = False
for sp_candidate in sps:
if response.sp_hash == response.message_signatures[0][0]:
found_sp_hash_debug = True
if sp_candidate.reward_chain_sp == response.message_signatures[1][0]:
is_sp_signatures = True
if found_sp_hash_debug:
assert is_sp_signatures
2020-11-26 09:22:13 +03:00
pospace = None
2020-12-03 16:49:14 +03:00
for plot_identifier, candidate_pospace in self.farmer.proofs_of_space[response.sp_hash]:
2020-11-26 09:22:13 +03:00
if plot_identifier == response.plot_identifier:
pospace = candidate_pospace
assert pospace is not None
2020-12-03 16:49:14 +03:00
computed_quality_string = pospace.verify_and_get_quality_string(
self.farmer.constants, response.challenge_hash, response.sp_hash
2020-12-03 16:49:14 +03:00
)
if computed_quality_string is None:
self.farmer.log.warning(f"Have invalid PoSpace {pospace}")
return
2020-11-26 09:22:13 +03:00
if is_sp_signatures:
(
challenge_chain_sp,
challenge_chain_sp_harv_sig,
) = response.message_signatures[0]
reward_chain_sp, reward_chain_sp_harv_sig = response.message_signatures[1]
2020-12-01 11:22:58 +03:00
for sk in self.farmer.get_private_keys():
2020-11-26 09:22:13 +03:00
pk = sk.get_g1()
if pk == response.farmer_pk:
agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk)
assert agg_pk == pospace.plot_public_key
farmer_share_cc_sp = AugSchemeMPL.sign(sk, challenge_chain_sp, agg_pk)
agg_sig_cc_sp = AugSchemeMPL.aggregate([challenge_chain_sp_harv_sig, farmer_share_cc_sp])
assert AugSchemeMPL.verify(agg_pk, challenge_chain_sp, agg_sig_cc_sp)
# This means it passes the sp filter
2020-12-03 16:49:14 +03:00
farmer_share_rc_sp = AugSchemeMPL.sign(sk, reward_chain_sp, agg_pk)
agg_sig_rc_sp = AugSchemeMPL.aggregate([reward_chain_sp_harv_sig, farmer_share_rc_sp])
assert AugSchemeMPL.verify(agg_pk, reward_chain_sp, agg_sig_rc_sp)
2020-12-10 19:34:56 +03:00
assert pospace.pool_public_key is not None
2020-12-03 16:49:14 +03:00
pool_pk = bytes(pospace.pool_public_key)
if pool_pk not in self.farmer.pool_sks_map:
self.farmer.log.error(
f"Don't have the private key for the pool key used by harvester: {pool_pk.hex()}"
2020-11-26 09:22:13 +03:00
)
return
2020-12-03 16:49:14 +03:00
pool_target: PoolTarget = PoolTarget(self.farmer.pool_target, uint32(0))
pool_target_signature: G2Element = AugSchemeMPL.sign(
self.farmer.pool_sks_map[pool_pk], bytes(pool_target)
)
request = farmer_protocol.DeclareProofOfSpace(
response.challenge_hash,
challenge_chain_sp,
signage_point_index,
2020-12-03 16:49:14 +03:00
reward_chain_sp,
pospace,
agg_sig_cc_sp,
agg_sig_rc_sp,
self.farmer.wallet_target,
pool_target,
pool_target_signature,
)
self.farmer.state_changed("proof", {"proof": request, "passed_filter": True})
2020-12-03 16:49:14 +03:00
msg = Message("declare_proof_of_space", request)
await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE)
return
2020-10-16 04:03:46 +03:00
2020-11-26 09:22:13 +03:00
else:
# This is a response with block signatures
2020-12-03 16:49:14 +03:00
for sk in self.farmer.get_private_keys():
2020-11-26 09:22:13 +03:00
(
foliage_sub_block_hash,
foliage_sub_block_sig_harvester,
) = response.message_signatures[0]
(
foliage_block_hash,
foliage_block_sig_harvester,
) = response.message_signatures[1]
pk = sk.get_g1()
if pk == response.farmer_pk:
agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk)
assert agg_pk == pospace.plot_public_key
foliage_sub_block_sig_farmer = AugSchemeMPL.sign(sk, foliage_sub_block_hash, agg_pk)
foliage_block_sig_farmer = AugSchemeMPL.sign(sk, foliage_block_hash, agg_pk)
foliage_sub_block_agg_sig = AugSchemeMPL.aggregate(
[foliage_sub_block_sig_harvester, foliage_sub_block_sig_farmer]
)
foliage_block_agg_sig = AugSchemeMPL.aggregate(
[foliage_block_sig_harvester, foliage_block_sig_farmer]
)
assert AugSchemeMPL.verify(agg_pk, foliage_sub_block_hash, foliage_sub_block_agg_sig)
assert AugSchemeMPL.verify(agg_pk, foliage_block_hash, foliage_block_agg_sig)
2020-12-10 19:34:56 +03:00
request_to_nodes = farmer_protocol.SignedValues(
2020-11-26 09:22:13 +03:00
computed_quality_string,
foliage_sub_block_agg_sig,
foliage_block_agg_sig,
)
2020-12-10 19:34:56 +03:00
msg = Message("signed_values", request_to_nodes)
2020-11-26 09:22:13 +03:00
await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE)
2020-10-16 04:03:46 +03:00
"""
FARMER PROTOCOL (FARMER <-> FULL NODE)
"""
@api_request
2020-11-26 09:22:13 +03:00
async def new_signage_point(self, new_signage_point: farmer_protocol.NewSignagePoint):
message = harvester_protocol.NewSignagePoint(
new_signage_point.challenge_hash,
new_signage_point.difficulty,
new_signage_point.sub_slot_iters,
new_signage_point.signage_point_index,
new_signage_point.challenge_chain_sp,
)
2020-10-16 04:03:46 +03:00
2020-11-26 09:22:13 +03:00
msg = Message("new_signage_point", message)
2020-10-16 04:03:46 +03:00
await self.farmer.server.send_to_all([msg], NodeType.HARVESTER)
if new_signage_point.challenge_chain_sp not in self.farmer.sps:
self.farmer.sps[new_signage_point.challenge_chain_sp] = []
self.farmer.sps[new_signage_point.challenge_chain_sp].append(new_signage_point)
2020-12-11 11:02:09 +03:00
self.farmer.cache_add_time[new_signage_point.challenge_chain_sp] = uint64(int(time.time()))
self.farmer.state_changed("new_signage_point", {"sp_hash": new_signage_point.challenge_chain_sp})
2020-10-16 04:03:46 +03:00
@api_request
2020-11-26 09:22:13 +03:00
async def request_signed_values(self, full_node_request: farmer_protocol.RequestSignedValues):
if full_node_request.quality_string not in self.farmer.quality_str_to_identifiers:
self.farmer.log.error(f"Do not have quality string {full_node_request.quality_string}")
return
(plot_identifier, challenge_hash, sp_hash, node_id) = self.farmer.quality_str_to_identifiers[
full_node_request.quality_string
]
2020-11-26 09:22:13 +03:00
request = harvester_protocol.RequestSignatures(
plot_identifier,
challenge_hash,
2020-12-03 16:49:14 +03:00
sp_hash,
[full_node_request.foliage_sub_block_hash, full_node_request.foliage_block_hash],
2020-11-26 09:22:13 +03:00
)
2020-10-16 04:03:46 +03:00
2020-11-26 09:22:13 +03:00
msg = Message("request_signatures", request)
await self.farmer.server.send_to_specific([msg], node_id)