def calculate_all_rewards(block, client: ContractingClient): total_stamps_to_split = RewardManager.stamps_in_block(block) master_ratio, delegate_ratio, burn_ratio, foundation_ratio, developer_ratio = \ client.get_var(contract='rewards', variable='S', arguments=['value']) master_reward = RewardManager.calculate_participant_reward( participant_ratio=master_ratio, number_of_participants=len( client.get_var(contract='masternodes', variable='S', arguments=['members'])), total_stamps_to_split=total_stamps_to_split) delegate_reward = RewardManager.calculate_participant_reward( participant_ratio=delegate_ratio, number_of_participants=len( client.get_var(contract='delegates', variable='S', arguments=['members'])), total_stamps_to_split=total_stamps_to_split) foundation_reward = RewardManager.calculate_participant_reward( participant_ratio=foundation_ratio, number_of_participants=1, total_stamps_to_split=total_stamps_to_split) developer_mapping = RewardManager.create_to_send_map( block=block, client=client, developer_ratio=developer_ratio) # burn does nothing, as the stamps are already deducted from supply return master_reward, delegate_reward, foundation_reward, developer_mapping
class TestDeveloperSubmission(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush() with open('../../contracting/contracts/submission.s.py') as f: contract = f.read() self.c.raw_driver.set_contract( name='submission', code=contract, ) self.c.raw_driver.commit() def test_submit_sets_developer(self): self.c.submit(test) dev = self.c.get_var('test', '__developer__') self.assertEqual(dev, 'stu') def test_change_developer_if_developer_works(self): self.c.submit(test) submission = self.c.get_contract('submission') submission.change_developer(contract='test', new_developer='not_stu') dev = self.c.get_var('test', '__developer__') self.assertEqual(dev, 'not_stu') def test_change_developer_prevents_new_change(self): self.c.submit(test) submission = self.c.get_contract('submission') submission.change_developer(contract='test', new_developer='not_stu') with self.assertRaises(AssertionError): submission.change_developer(contract='test', new_developer='woohoo') def test_cannot_import_submission(self): self.c.submit(import_submission) imp_con = self.c.get_contract('import_submission') with self.assertRaises(AssertionError): imp_con.haha()
def distribute_rewards(master_reward, delegate_reward, foundation_reward, developer_mapping, client: ContractingClient): stamp_cost = client.get_var(contract='stamp_cost', variable='S', arguments=['value']) master_reward /= stamp_cost delegate_reward /= stamp_cost foundation_reward /= stamp_cost log.info( f'Master reward: {format(master_reward, ".4f")}t per master. ' f'Delegate reward: {format(delegate_reward, ".4f")}t per delegate. ' f'Foundation reward: {format(foundation_reward, ".4f")}t.') for m in client.get_var(contract='masternodes', variable='S', arguments=['members']): RewardManager.add_to_balance(vk=m, amount=master_reward, client=client) for d in client.get_var(contract='delegates', variable='S', arguments=['members']): RewardManager.add_to_balance(vk=d, amount=delegate_reward, client=client) foundation_wallet = client.get_var(contract='foundation', variable='owner') RewardManager.add_to_balance(vk=foundation_wallet, amount=foundation_reward, client=client) # Send rewards to each developer calculated from the block for recipient, amount in developer_mapping.items(): dev_reward = round((amount / stamp_cost), DUST_EXPONENT) RewardManager.add_to_balance(vk=recipient, amount=dev_reward, client=client) log.info(f'Remainder is burned.')
def register_policies(client: ContractingClient): # add to election house election_house = client.get_contract('election_house') policies_to_register = [ 'masternodes', 'delegates', 'rewards', 'stamp_cost' ] for policy in policies_to_register: if client.get_var(contract='election_house', variable='policies', arguments=[policy]) is None: election_house.register_policy(contract=policy)
def add_to_balance(vk, amount, client: ContractingClient): current_balance = client.get_var(contract='currency', variable='balances', arguments=[vk], mark=False) if current_balance is None: current_balance = ContractingDecimal(0) amount = ContractingDecimal(amount) client.set_var(contract='currency', variable='balances', arguments=[vk], value=amount + current_balance, mark=True)
def create_to_send_map(block, developer_ratio, client: ContractingClient): # Find all transactions and the developer of the contract. # Count all stamps used by people and multiply it by the developer ratio send_map = defaultdict(lambda: 0) for sb in block['subblocks']: for tx in sb['transactions']: contract = tx['transaction']['payload']['contract'] recipient = client.get_var(contract=contract, variable='__developer__') send_map[recipient] += (tx['stamps_used'] * developer_ratio) for developer in send_map.keys(): send_map[developer] /= len(send_map) return send_map
class TestRewards(TestCase): def setUp(self): self.client = ContractingClient() self.rewards = rewards.RewardManager() def tearDown(self): self.client.flush() def sync(self): sync.setup_genesis_contracts(['stu', 'raghu', 'steve'], ['tejas', 'alex2'], client=self.client) def test_contract_exists_false_before_sync(self): self.assertFalse( self.rewards.contract_exists('stamp_cost', self.client)) def test_contract_exists_true_after_sync(self): # Sync contracts self.sync() self.assertTrue(self.rewards.contract_exists('stamp_cost', self.client)) def test_is_setup_false_before_sync(self): self.assertFalse(self.rewards.is_setup(self.client)) def test_is_setup_true_after_sync(self): self.sync() self.assertTrue(self.rewards.is_setup(self.client)) def test_add_to_balance_if_none_sets(self): self.rewards.add_to_balance('stu', 123, self.client) bal = self.client.get_var('currency', variable='balances', arguments=['stu']) self.assertEqual(bal, 123) def test_add_to_balance_twice_sets_accordingly(self): self.rewards.add_to_balance('stu', 123, self.client) bal = self.client.get_var('currency', variable='balances', arguments=['stu']) self.assertEqual(bal, 123) self.rewards.add_to_balance('stu', 123, self.client) bal = self.client.get_var('currency', variable='balances', arguments=['stu']) self.assertEqual(bal, 246) def test_calculate_rewards_returns_accurate_amounts_per_participant_group( self): self.sync() self.client.set_var(contract='rewards', variable='S', arguments=['value'], value=[0.4, 0.3, 0.1, 0.1, 0.1]) m, d, f, mapping = self.rewards.calculate_all_rewards( client=self.client, block=BLOCK) reconstructed = (m * 3) + (d * 2) + (f * 1) + (f * 1) + (f * 1) self.assertAlmostEqual(reconstructed, self.rewards.stamps_in_block(BLOCK)) def test_calculate_participant_reward_shaves_off_dust(self): rounded_reward = self.rewards.calculate_participant_reward( participant_ratio=1, number_of_participants=1, total_stamps_to_split=1.0000000000001) self.assertEqual(rounded_reward, 1) def test_distribute_rewards_adds_to_all_wallets(self): self.sync() self.client.set_var(contract='rewards', variable='S', arguments=['value'], value=[0.4, 0.3, 0.1, 0.1, 0.1]) self.client.set_var(contract='foundation', variable='owner', value='xxx') self.client.set_var(contract='stamp_cost', variable='S', arguments=['value'], value=100) self.client.set_var(contract='thing_1', variable='__developer__', value='stu2') self.client.set_var(contract='thing_2', variable='__developer__', value='jeff') self.client.set_var(contract='thing_3', variable='__developer__', value='alex') total_tau_to_split = 4900 m, d, f, mapping = self.rewards.calculate_all_rewards( client=self.client, block=BLOCK) self.rewards.distribute_rewards(m, d, f, mapping, client=self.client) masters = self.client.get_var(contract='masternodes', variable='S', arguments=['members']) delegates = self.client.get_var(contract='delegates', variable='S', arguments=['members']) for mn in masters: current_balance = self.client.get_var(contract='currency', variable='balances', arguments=[mn], mark=False) self.assertEqual(current_balance, m / 100) for dl in delegates: current_balance = self.client.get_var(contract='currency', variable='balances', arguments=[dl], mark=False) self.assertEqual(current_balance, d / 100) current_balance = self.client.get_var(contract='currency', variable='balances', arguments=['xxx'], mark=False) self.assertEqual(current_balance, f / 100) def test_stamps_in_block(self): block = { 'number': 2, 'subblocks': [{ 'transactions': [{ 'stamps_used': 1000 }, { 'stamps_used': 2000 }, { 'stamps_used': 3000 }] }, { 'transactions': [{ 'stamps_used': 4500 }, { 'stamps_used': 1250 }, { 'stamps_used': 2750 }] }] } self.assertEqual(self.rewards.stamps_in_block(block), 14500) def test_issue_rewards_full_loop_works(self): self.sync() self.client.set_var(contract='rewards', variable='S', arguments=['value'], value=[0.4, 0.3, 0.1, 0.1, 0.1]) self.client.set_var(contract='foundation', variable='owner', value='xxx') self.client.set_var(contract='stamp_cost', variable='S', arguments=['value'], value=100) self.client.set_var(contract='thing_1', variable='__developer__', value='stu2') self.client.set_var(contract='thing_2', variable='__developer__', value='jeff') self.client.set_var(contract='thing_3', variable='__developer__', value='alex') block = { 'number': 1, 'subblocks': [{ 'transactions': [{ 'stamps_used': 1000, 'transaction': { 'payload': { 'contract': 'thing_1' } } }, { 'stamps_used': 2000, 'transaction': { 'payload': { 'contract': 'thing_2' } } }, { 'stamps_used': 3000, 'transaction': { 'payload': { 'contract': 'thing_3' } } }] }, { 'transactions': [{ 'stamps_used': 4500, 'transaction': { 'payload': { 'contract': 'thing_1' } } }, { 'stamps_used': 1250, 'transaction': { 'payload': { 'contract': 'thing_1' } } }, { 'stamps_used': 2750, 'transaction': { 'payload': { 'contract': 'thing_2' } } }] }] } # tau to distribute should be 145 stamps = self.rewards.stamps_in_block(block) tau = stamps / 100 self.assertEqual(tau, 145) self.rewards.issue_rewards(block, client=self.client) # Stu is owed: 6750 stamps / 100 / 3 = # Jeff is owed: 4750 stamps / 100 / 3= 47.5 # Alex is owed: m, d, f, mapping = self.rewards.calculate_all_rewards( client=self.client, block=block) masters = self.client.get_var(contract='masternodes', variable='S', arguments=['members']) delegates = self.client.get_var(contract='delegates', variable='S', arguments=['members']) for mn in masters: current_balance = self.client.get_var(contract='currency', variable='balances', arguments=[mn], mark=False) self.assertEqual(current_balance, m / 100) for dl in delegates: current_balance = self.client.get_var(contract='currency', variable='balances', arguments=[dl], mark=False) self.assertEqual(current_balance, d / 100) current_balance = self.client.get_var(contract='currency', variable='balances', arguments=['xxx'], mark=False) self.assertEqual(current_balance, f / 100) for dev in mapping.keys(): current_balance = self.client.get_var(contract='currency', variable='balances', arguments=[dev], mark=False) self.assertAlmostEqual(current_balance, mapping[dev] / 100)
class Node: def __init__(self, socket_base, ctx: zmq.asyncio.Context, wallet, constitution: dict, bootnodes={}, blocks=storage.BlockStorage(), driver=ContractDriver(), debug=True, store=False, seed=None, bypass_catchup=False, node_type=None, genesis_path=lamden.contracts.__path__[0], reward_manager=rewards.RewardManager(), nonces=storage.NonceStorage()): self.driver = driver self.nonces = nonces self.store = store self.seed = seed self.blocks = blocks self.event_writer = EventWriter() self.log = get_logger('Base') self.log.propagate = debug self.socket_base = socket_base self.wallet = wallet self.ctx = ctx self.genesis_path = genesis_path self.client = ContractingClient(driver=self.driver, submission_filename=genesis_path + '/submission.s.py') self.bootnodes = bootnodes self.constitution = constitution self.seed_genesis_contracts() self.socket_authenticator = authentication.SocketAuthenticator( bootnodes=self.bootnodes, ctx=self.ctx, client=self.client) self.upgrade_manager = upgrade.UpgradeManager(client=self.client, wallet=self.wallet, node_type=node_type) self.router = router.Router(socket_id=socket_base, ctx=self.ctx, wallet=wallet, secure=True) self.network = network.Network(wallet=wallet, ip_string=socket_base, ctx=self.ctx, router=self.router) self.new_block_processor = NewBlock(driver=self.driver) self.router.add_service( NEW_BLOCK_SERVICE, self.new_block_processor) # Add this after catch up? self.running = False self.upgrade = False self.reward_manager = reward_manager self.current_height = storage.get_latest_block_height(self.driver) self.current_hash = storage.get_latest_block_hash(self.driver) self.bypass_catchup = bypass_catchup def seed_genesis_contracts(self): self.log.info('Setting up genesis contracts.') sync.setup_genesis_contracts( initial_masternodes=self.constitution['masternodes'], initial_delegates=self.constitution['delegates'], client=self.client, filename=self.genesis_path + '/genesis.json', root=self.genesis_path) async def catchup(self, mn_seed, mn_vk): # Get the current latest block stored and the latest block of the network self.log.info('Running catchup.') current = self.current_height latest = await get_latest_block_height(ip=mn_seed, vk=mn_vk, wallet=self.wallet, ctx=self.ctx) self.log.info( f'Current block: {current}, Latest available block: {latest}') if latest == 0 or latest is None or type(latest) == dict: self.log.info('No need to catchup. Proceeding.') return # Increment current by one. Don't count the genesis block. if current == 0: current = 1 # Find the missing blocks process them for i in range(current, latest + 1): block = None while block is None: block = await get_block(block_num=i, ip=mn_seed, vk=mn_vk, wallet=self.wallet, ctx=self.ctx) self.process_new_block(block) # Process any blocks that were made while we were catching up while len(self.new_block_processor.q) > 0: block = self.new_block_processor.q.pop(0) self.process_new_block(block) def should_process(self, block): try: self.log.info(f'Processing block #{block.get("number")}') except: self.log.error('Malformed block :(') return False # Test if block failed immediately if block == {'response': 'ok'}: return False if block['hash'] == 'f' * 64: self.log.error('Failed Block! Not storing.') return False # Get current metastate # if len(block['subblocks']) < 1: # return False # Test if block contains the same metastate # if block['number'] != self.current_height + 1: # self.log.info(f'Block #{block["number"]} != {self.current_height + 1}. ' # f'Node has probably already processed this block. Continuing.') # return False # if block['previous'] != self.current_hash: # self.log.error('Previous block hash != Current hash. Cryptographically invalid. Not storing.') # return False # If so, use metastate and subblocks to create the 'expected' block # expected_block = canonical.block_from_subblocks( # subblocks=block['subblocks'], # previous_hash=self.current_hash, # block_num=self.current_height + 1 # ) # Return if the block contains the expected information # good = block == expected_block # if good: # self.log.info(f'Block #{block["number"]} passed all checks. Store.') # else: # self.log.error(f'Block #{block["number"]} has an encoding problem. Do not store.') # # return good return True def update_state(self, block): self.driver.clear_pending_state() # Check if the block is valid if self.should_process(block): self.log.info('Storing new block.') # Commit the state changes and nonces to the database storage.update_state_with_block(block=block, driver=self.driver, nonces=self.nonces) self.log.info('Issuing rewards.') # Calculate and issue the rewards for the governance nodes self.reward_manager.issue_rewards(block=block, client=self.client) #self.nonces.flush_pending() self.log.info('Updating metadata.') self.current_height = storage.get_latest_block_height(self.driver) self.current_hash = storage.get_latest_block_hash(self.driver) self.new_block_processor.clean(self.current_height) def process_new_block(self, block): # Update the state and refresh the sockets so new nodes can join self.update_state(block) self.socket_authenticator.refresh_governance_sockets() # Store the block if it's a masternode if self.store: encoded_block = encode(block) encoded_block = json.loads(encoded_block) self.blocks.store_block(encoded_block) # create Event File self.event_writer.write_event( Event(topics=[NEW_BLOCK_EVENT], data=encoded_block)) # Prepare for the next block by flushing out driver and notification state # self.new_block_processor.clean() # Finally, check and initiate an upgrade if one needs to be done self.driver.commit() self.driver.clear_pending_state() gc.collect() # Force memory cleanup every block self.nonces.flush_pending() async def start(self): asyncio.ensure_future(self.router.serve()) # Get the set of VKs we are looking for from the constitution argument vks = self.constitution['masternodes'] + self.constitution['delegates'] for node in self.bootnodes.keys(): self.socket_authenticator.add_verifying_key(node) self.socket_authenticator.configure() # Use it to boot up the network await self.network.start(bootnodes=self.bootnodes, vks=vks) if not self.bypass_catchup: masternode_ip = None masternode = None if self.seed is not None: for k, v in self.bootnodes.items(): self.log.info(k, v) if v == self.seed: masternode = k masternode_ip = v else: masternode = self.constitution['masternodes'][0] masternode_ip = self.network.peers[masternode] self.log.info(f'Masternode Seed VK: {masternode}') # Use this IP to request any missed blocks await self.catchup(mn_seed=masternode_ip, mn_vk=masternode) # Refresh the sockets to accept new nodes self.socket_authenticator.refresh_governance_sockets() # Start running self.running = True def stop(self): # Kill the router and throw the running flag to stop the loop self.router.stop() self.running = False def _get_member_peers(self, contract_name): members = self.client.get_var(contract=contract_name, variable='S', arguments=['members']) member_peers = dict() for member in members: ip = self.network.peers.get(member) if ip is not None: member_peers[member] = ip return member_peers def get_delegate_peers(self): return self._get_member_peers('delegates') def get_masternode_peers(self): return self._get_member_peers('masternodes') def make_constitution(self): return { 'masternodes': self.get_masternode_peers(), 'delegates': self.get_delegate_peers() }
def transaction_is_valid(transaction, expected_processor, client: ContractingClient, nonces: storage.NonceStorage, strict=True, tx_per_block=15, timeout=5): # Check basic formatting so we can access via __getitem__ notation without errors if not check_format(transaction, rules.TRANSACTION_RULES): return TransactionFormattingError transaction_is_not_expired(transaction, timeout) # Put in to variables for visual ease processor = transaction['payload']['processor'] sender = transaction['payload']['sender'] # Checks if correct processor and if signature is valid check_tx_formatting(transaction, expected_processor) # Gets the expected nonces nonce, pending_nonce = get_nonces(sender, processor, nonces) # Get the provided nonce tx_nonce = transaction['payload']['nonce'] # Check to see if the provided nonce is valid to what we expect and # if there are less than the max pending txs in the block get_new_pending_nonce(tx_nonce, nonce, pending_nonce, strict=strict, tx_per_block=tx_per_block) # Get the senders balance and the current stamp rate balance = client.get_var(contract='currency', variable='balances', arguments=[sender], mark=False) stamp_rate = client.get_var(contract='stamp_cost', variable='S', arguments=['value'], mark=False) contract = transaction['payload']['contract'] func = transaction['payload']['function'] stamps_supplied = transaction['payload']['stamps_supplied'] if stamps_supplied is None: stamps_supplied = 0 if stamp_rate is None: stamp_rate = 0 if balance is None: balance = 0 # Get how much they are sending amount = transaction['payload']['kwargs'].get('amount') if amount is None: amount = 0 # Check if they have enough stamps for the operation has_enough_stamps(balance, stamp_rate, stamps_supplied, contract=contract, function=func, amount=amount) # Check if contract name is valid name = transaction['payload']['kwargs'].get('name') contract_name_is_valid(contract, func, name)