Skip to content

Commit 9a3a1e4

Browse files
committed
feat(mergedmining): support dummy mining on coordinator
1 parent da4a89c commit 9a3a1e4

File tree

2 files changed

+59
-18
lines changed

2 files changed

+59
-18
lines changed

hathor/cli/merged_mining.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ def create_parser() -> ArgumentParser:
3030
parser.add_argument('--debug-listen', help='Port to listen for Debug API', type=int, required=False)
3131
parser.add_argument('--hathor-api', help='Endpoint of the Hathor API (without version)', type=str, required=True)
3232
parser.add_argument('--hathor-address', help='Hathor address to send funds to', type=str, required=False)
33-
parser.add_argument('--bitcoin-rpc', help='Endpoint of the Bitcoin RPC', type=str, required=True)
33+
rpc = parser.add_mutually_exclusive_group(required=True)
34+
rpc.add_argument('--bitcoin-rpc', help='Endpoint of the Bitcoin RPC', type=str)
3435
parser.add_argument('--bitcoin-address', help='Bitcoin address to send funds to', type=str, required=False)
36+
rpc.add_argument('--dummy-merged-mining', help='Use zeroed bits to simulate a dummy merged mining',
37+
action='store_true')
38+
parser.add_argument('--dummy-merkle-len', help='Merkle path length to simulate when doing dummy merged mining',
39+
type=int, required=False)
3540
parser.add_argument('--min-diff', help='Minimum difficulty to set for jobs', type=int, required=False)
3641
return parser
3742

@@ -45,7 +50,14 @@ def execute(args: Namespace) -> None:
4550

4651
loop = asyncio.get_event_loop()
4752

48-
bitcoin_rpc = BitcoinRPC(args.bitcoin_rpc)
53+
bitcoin_rpc: BitcoinRPC | None
54+
if args.bitcoin_rpc is not None:
55+
# XXX: plain assert because argparse should already ensure it's correct
56+
assert not args.dummy_merged_mining
57+
bitcoin_rpc = BitcoinRPC(args.bitcoin_rpc)
58+
else:
59+
assert args.dummy_merged_mining
60+
bitcoin_rpc = None
4961
hathor_client = HathorClient(args.hathor_api)
5062
# TODO: validate addresses?
5163
merged_mining = MergedMiningCoordinator(
@@ -56,8 +68,9 @@ def execute(args: Namespace) -> None:
5668
address_from_login=not (args.hathor_address and args.bitcoin_address),
5769
min_difficulty=args.min_diff,
5870
)
59-
logger.info('start Bitcoin RPC', url=args.bitcoin_rpc)
60-
loop.run_until_complete(bitcoin_rpc.start())
71+
if bitcoin_rpc is not None:
72+
logger.info('start Bitcoin RPC', url=args.bitcoin_rpc)
73+
loop.run_until_complete(bitcoin_rpc.start())
6174
logger.info('start Hathor Client', url=args.hathor_api)
6275
loop.run_until_complete(hathor_client.start())
6376
logger.info('start Merged Mining Server', listen=f'0.0.0.0:{args.port}')
@@ -89,7 +102,8 @@ def execute(args: Namespace) -> None:
89102
loop.run_until_complete(mm_server.wait_closed())
90103
loop.run_until_complete(merged_mining.stop())
91104
loop.run_until_complete(hathor_client.stop())
92-
loop.run_until_complete(bitcoin_rpc.stop())
105+
if bitcoin_rpc is not None:
106+
loop.run_until_complete(bitcoin_rpc.stop())
93107
loop.close()
94108
logger.info('bye')
95109

hathor/merged_mining/coordinator.py

+40-13
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
# PROPAGATION_FAILED = {'code': 33, 'message': 'Solution propagation failed'}
7272
DUPLICATE_SOLUTION = {'code': 34, 'message': 'Solution already submitted'}
7373

74+
ZEROED_4: bytes = b'\0' * 4
75+
ZEROED_32: bytes = b'\0' * 32
76+
7477

7578
class HathorCoordJob(NamedTuple):
7679
""" Data class used to send a job's work to Hathor Stratum.
@@ -690,6 +693,8 @@ async def submit_to_bitcoin(self, job: SingleMinerJob, work: SingleMinerWork) ->
690693
""" Submit work to Bitcoin RPC.
691694
"""
692695
bitcoin_rpc = self.coordinator.bitcoin_rpc
696+
if bitcoin_rpc is None:
697+
return
693698
# bitcoin_block = job.build_bitcoin_block(work) # XXX: too expensive for now
694699
bitcoin_block_header = job.build_bitcoin_block_header(work)
695700
block_hash = Hash(bitcoin_block_header.hash)
@@ -799,6 +804,21 @@ class BitcoinCoordJob(NamedTuple):
799804
witness_commitment: Optional[bytes] = None
800805
append_to_input: bool = True
801806

807+
@classmethod
808+
def zeroed(cls, merkle_path_len: int = 0) -> 'BitcoinCoordJob':
809+
return cls(
810+
version=0,
811+
previous_block_hash=ZEROED_32,
812+
coinbase_value=0,
813+
target=ZEROED_32,
814+
min_time=0,
815+
size_limit=0,
816+
bits=ZEROED_4,
817+
height=0,
818+
transactions=[],
819+
merkle_path=tuple([ZEROED_32] * merkle_path_len),
820+
)
821+
802822
@classmethod
803823
def from_dict(cls, params: dict) -> 'BitcoinCoordJob':
804824
r""" Convert from dict of the properties returned from Bitcoin RPC.
@@ -970,7 +990,7 @@ def make_coinbase_transaction(self, hathor_block_hash: bytes, payback_script_bit
970990
if self.witness_commitment is not None:
971991
segwit_output = BitcoinTransactionOutput(0, self.witness_commitment)
972992
outputs.append(segwit_output)
973-
coinbase_input.script_witness.append(b'\0' * 32)
993+
coinbase_input.script_witness.append(ZEROED_32)
974994

975995
# append now because segwit presence may change this
976996
inputs.append(coinbase_input)
@@ -1112,10 +1132,11 @@ class MergedMiningCoordinator:
11121132
MAX_XNONCE1 = 2**XNONCE1_SIZE - 1
11131133
MAX_RECONNECT_BACKOFF = 30
11141134

1115-
def __init__(self, bitcoin_rpc: IBitcoinRPC, hathor_client: IHathorClient,
1116-
payback_address_bitcoin: Optional[str], payback_address_hathor: Optional[str],
1117-
address_from_login: bool = True, min_difficulty: Optional[int] = None,
1118-
sequential_xnonce1: bool = False, rng: Optional[Random] = None):
1135+
def __init__(self, bitcoin_rpc: IBitcoinRPC | None, hathor_client: IHathorClient,
1136+
payback_address_bitcoin: str | None, payback_address_hathor: str | None,
1137+
address_from_login: bool = True, min_difficulty: int | None = None,
1138+
sequential_xnonce1: bool = False, rng: Random | None = None,
1139+
dummy_merkle_path_len: int | None = None):
11191140
self.log = logger.new()
11201141
if rng is None:
11211142
rng = Random()
@@ -1146,6 +1167,7 @@ def __init__(self, bitcoin_rpc: IBitcoinRPC, hathor_client: IHathorClient,
11461167
self.started_at = 0.0
11471168
self.strip_all_transactions = False
11481169
self.strip_segwit_transactions = False
1170+
self.dummy_merkle_path_len = dummy_merkle_path_len or 0
11491171

11501172
@property
11511173
def uptime(self) -> float:
@@ -1187,7 +1209,10 @@ async def start(self) -> None:
11871209
"""
11881210
loop = asyncio.get_event_loop()
11891211
self.started_at = time.time()
1190-
self.update_bitcoin_block_task = loop.create_task(self.update_bitcoin_block())
1212+
if self.bitcoin_rpc is not None:
1213+
self.update_bitcoin_block_task = loop.create_task(self.update_bitcoin_block())
1214+
else:
1215+
self.bitcoin_coord_job = BitcoinCoordJob.zeroed(self.dummy_merkle_path_len)
11911216
self.update_hathor_block_task = loop.create_task(self.update_hathor_block())
11921217

11931218
async def stop(self) -> None:
@@ -1212,6 +1237,7 @@ async def stop(self) -> None:
12121237
async def update_bitcoin_block(self) -> None:
12131238
""" Task that continuously polls block templates from bitcoin.get_block_template
12141239
"""
1240+
assert self.bitcoin_rpc is not None
12151241
backoff = 1
12161242
longpoll_id = None
12171243
while True:
@@ -1350,13 +1376,14 @@ async def update_merged_block(self) -> None:
13501376
merkle_root = build_merkle_root(list(tx.txid for tx in block_proposal.transactions))
13511377
if merkle_root != block_proposal.header.merkle_root:
13521378
self.log.warn('bad merkle root', expected=merkle_root.hex(), got=block_proposal.header.merkle_root.hex())
1353-
error = await self.bitcoin_rpc.verify_block_proposal(block=bytes(block_proposal))
1354-
if error is not None:
1355-
self.log.warn('proposed block is invalid, skipping update', error=error)
1356-
else:
1357-
self.next_merged_job = merged_job
1358-
self.update_jobs()
1359-
self.log.debug('merged job updated')
1379+
if self.bitcoin_rpc is not None:
1380+
error = await self.bitcoin_rpc.verify_block_proposal(block=bytes(block_proposal))
1381+
if error is not None:
1382+
self.log.warn('proposed block is invalid, skipping update', error=error)
1383+
return
1384+
self.next_merged_job = merged_job
1385+
self.update_jobs()
1386+
self.log.debug('merged job updated')
13601387

13611388
def status(self) -> dict[Any, Any]:
13621389
""" Build status dict with useful metrics for use in MM Status API.

0 commit comments

Comments
 (0)