def test_revert_message_txn(self): tx = MessageTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 100) storage_key = state_container.paginated_tx_hash.generate_key( self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual( [], state_container.paginated_tx_hash.key_value[storage_key])
def test_revert_slave_txn(self): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 100) storage_key = state_container.paginated_tx_hash.generate_key( self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual( [], state_container.paginated_tx_hash.key_value[storage_key]) self.assertIn((tx.addr_from, tx.slave_pks[0]), state_container.slaves.data) data = state_container.slaves.data[(tx.addr_from, tx.slave_pks[0])] self.assertIsInstance(data, SlaveMetadata) self.assertEqual(tx.access_types[0], data.access_type) self.assertEqual(tx.txhash, data.tx_hash)
def test_revert_coinbase_txn(self, m_logger): """ Alice earned some coins. Undo this. """ tx = CoinBase.create(config.dev, self.amount, self.alice.address, self.mock_blockheader.block_number) addresses_state = { config.dev.coinbase_address: OptimizedAddressState.get_default(config.dev.coinbase_address), self.alice.address: OptimizedAddressState.get_default(self.alice.address), } addresses_state[config.dev.coinbase_address].pbdata.balance = 1000000 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=self.mock_blockheader.block_number, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertEqual(1000000, addresses_state[config.dev.coinbase_address].balance) storage_key = state_container.paginated_tx_hash.generate_key(config.dev.coinbase_address, 1) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(0, addresses_state[self.alice.address].balance) storage_key = state_container.paginated_tx_hash.generate_key(config.dev.coinbase_address, 1) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key])
def test_apply(self): multi_sig_address = MultiSigAddressState.generate_multi_sig_address( b'') multi_sig_address_state = MultiSigAddressState.create( creation_tx_hash=b'', balance=100, signatories=[self.alice.address, self.bob.address], weights=[4, 6], threshold=5, transaction_hash_count=0) alice_address_state = OptimizedAddressState.get_default( self.alice.address) alice_address_state.pbdata.balance = 5 bob_address_state = OptimizedAddressState.get_default(self.bob.address) addresses_state = { self.alice.address: alice_address_state, self.bob.address: bob_address_state, multi_sig_address: multi_sig_address_state, } tx = MultiSigSpend.create(multi_sig_address=multi_sig_address, addrs_to=[self.bob.address], amounts=[100], expiry_block_number=15000, fee=5, xmss_pk=self.alice.pk) tx.sign(self.alice) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, tx.ots_key)) tx.apply(self.state, state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, tx.ots_key)) self.assertIn(tx.txhash, state_container.votes_stats) vote_stats = state_container.votes_stats[tx.txhash] self.assertEqual(vote_stats.shared_key, tx.txhash) self.assertEqual(vote_stats.total_weight, 0) self.assertEqual(vote_stats.signatories, multi_sig_address_state.signatories)
def test_validate_all(self): """ TODO: Check by signing txn from a non signatory address """ multi_sig_address = MultiSigAddressState.generate_multi_sig_address( b'') tx = MultiSigSpend.create(multi_sig_address=multi_sig_address, addrs_to=[self.bob.address], amounts=[100], expiry_block_number=15000, fee=5, xmss_pk=self.random.pk, master_addr=self.alice.address) tx.sign(self.random) tx._data.nonce = 1 alice_address_state = OptimizedAddressState.get_default( address=self.alice.address) alice_address_state.pbdata.balance = 5 random_address_state = OptimizedAddressState.get_default( address=self.random.address) multi_sig_address_state = MultiSigAddressState.create( creation_tx_hash=b'', balance=100, signatories=[self.alice.address, self.bob.address], weights=[4, 6], threshold=5, transaction_hash_count=0) addresses_state = { self.alice.address: alice_address_state, self.random.address: random_address_state, multi_sig_address: multi_sig_address_state, } slaves = Indexer(b'slave', None) slaves.data[(self.alice.address, self.random.pk)] = SlaveMetadata(access_type=0) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=slaves, lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=10, total_coin_supply=100, current_dev_config=config.dev, write_access=False, my_db=self.state._db, batch=None) result = tx.validate_all(state_container) self.assertTrue(result) tx._data.nonce = 2 result = tx.validate_all(state_container) self.assertFalse(result) # False as nonce is invalid
def test_revert_transfer_token_txn_multi_send(self): """ Alice has 1100 tokens and 100 QRL, Bob and Slave have none. Alice sends some tokens to Bob and Slave. Undo this. """ slave = get_slave_xmss() self.params["addrs_to"] = [self.bob.address, slave.address] self.params["amounts"] = [100, 100] tx = TransferTokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 addresses_state[self.bob.address].pbdata.balance = 0 addresses_state[slave.address] = OptimizedAddressState.get_default( slave.address) tokens = Indexer(b'token', None) tokens.data[(self.alice.address, tx.token_txhash)] = TokenBalance(balance=1100) state_container = StateContainer(addresses_state=addresses_state, tokens=tokens, slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, 0) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) self.assertEqual(addresses_state[self.alice.address].balance, 100) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual(addresses_state[slave.address].balance, 0) self.assertEqual( tokens.data[(self.alice.address, tx.token_txhash)].balance, 1100) self.assertEqual( tokens.data[(self.bob.address, tx.token_txhash)].balance, 0) self.assertEqual(tokens.data[(slave.address, tx.token_txhash)].balance, 0)
def test_validate_extended(self, m_validate_slave, m_logger): """ Message._validate_extended checks for: 1. valid master/slave 2. negative fee, negative total token amounts transferred 3. addr_from has enough funds for the fee 4. addr_from ots_key reuse """ m_addr_from_state = Mock(autospec=OptimizedAddressState, name='addr_from State', balance=100) m_addr_from_pk_state = Mock(autospec=OptimizedAddressState, name='addr_from_pk State') m_addr_from_pk_state.ots_key_reuse.return_value = False addresses_state = {self.alice.address: m_addr_from_state} tx = MessageTransaction.create(**self.params) tx.sign(self.alice) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=None, batch=None) result = tx._validate_extended(state_container) self.assertTrue(result) # fee = -1 with patch('qrl.core.txs.MessageTransaction.MessageTransaction.fee', new_callable=PropertyMock) as m_fee: m_fee.return_value = -1 result = tx._validate_custom() self.assertFalse(result) # balance = 0, cannot pay the Transaction fee m_addr_from_state.balance = 0 result = tx._validate_extended(state_container) self.assertFalse(result) m_addr_from_state.balance = 100 self.params["message_hash"] = b'T' * 81 # Validation should fail, as we have entered a message of more than 80 lengths tx = MessageTransaction.create(**self.params) self.assertFalse(tx._validate_extended(state_container))
def test_apply_token_txn_signed_by_slave_xmss(self): """ Alice creates a token, gives herself and Bob some tokens. But she uses a XMSS slave to sign it. """ initial_balances = [qrl_pb2.AddressAmount(address=self.alice.address, amount=1000), qrl_pb2.AddressAmount(address=self.bob.address, amount=2000)] slave = get_slave_xmss() self.params["initial_balances"] = initial_balances self.params["xmss_pk"] = slave.pk self.params["master_addr"] = self.alice.address tx = TokenTransaction.create(**self.params) tx.sign(slave) # Now that we have the Slave XMSS address, we should add it to AddressState so that apply_state_changes() # can do something with it addresses_state = dict(self.addresses_state) addresses_state[slave.address] = OptimizedAddressState.get_default(slave.address) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 99) self.assertEqual(3, len(state_container.paginated_tx_hash.key_value)) storage_key = state_container.paginated_tx_hash.generate_key(self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(self.bob.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(slave.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(2, len(state_container.tokens.data)) self.assertIn((self.bob.address, tx.txhash), state_container.tokens.data) self.assertEqual(1000, state_container.tokens.data[(self.alice.address, tx.txhash)].balance) self.assertEqual(2000, state_container.tokens.data[(self.bob.address, tx.txhash)].balance) self.assertTrue(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(slave.address, 0)) self.assertFalse(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.alice.address, 0)) self.assertFalse(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.bob.address, 0))
def test_revert_transfer_txn(self, m_logger): """ Alice has sent 100 coins to Bob, using 1 as Transaction fee. Now we need to undo this. """ self.tx.sign(self.alice) ots_key = self.alice.ots_index - 1 addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.bob.address: OptimizedAddressState.get_default(self.bob.address) } addresses_state[self.alice.address].pbdata.balance = 200 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, ots_key) state_container.paginated_bitfield.put_addresses_bitfield(None) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.tx.apply(self.state, state_container) self.tx.revert(self.state, state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.assertEqual(0, addresses_state[self.alice.address].nonce) self.assertEqual(200, addresses_state[self.alice.address].balance) self.assertEqual(0, addresses_state[self.bob.address].balance) storage_key = state_container.paginated_tx_hash.generate_key( self.alice.address, 1) self.assertEqual( [], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key( self.bob.address, 1) self.assertEqual( [], state_container.paginated_tx_hash.key_value[storage_key])
def test_revert_token_txn(self): """ Same setup as in test_apply_state_changes(). This time though, the changes have already been applied, and we would like to roll them back. """ initial_balances = [qrl_pb2.AddressAmount(address=self.alice.address, amount=1000), qrl_pb2.AddressAmount(address=self.bob.address, amount=2000)] self.params["initial_balances"] = initial_balances tx = TokenTransaction.create(**self.params) tx.sign(self.alice) # Apply the changes! tx = TokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) # After applying the Transaction, it should be as if Alice had never created the tokens in the first place. tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 100) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual(2, len(state_container.paginated_tx_hash.key_value)) storage_key = state_container.paginated_tx_hash.generate_key(self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(self.bob.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(2, len(state_container.tokens.data)) self.assertIn((self.alice.address, tx.txhash), state_container.tokens.data) self.assertEqual(0, state_container.tokens.data[(self.alice.address, tx.txhash)].balance) self.assertIn((self.bob.address, tx.txhash), state_container.tokens.data) self.assertEqual(0, state_container.tokens.data[(self.bob.address, tx.txhash)].balance) self.assertEqual(0, addresses_state[self.alice.address].nonce)
def test_revert_state_changes_for_PK_master_slave_XMSS(self, m_logger): addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.slave.address: OptimizedAddressState.get_default(self.slave.address) } addresses_state[self.alice.address].pbdata.balance = 101 addresses_state[self.slave.address].pbdata.nonce = 1 tx = TransferTransaction.create(addrs_to=[self.bob.address], amounts=[100], message_data=None, fee=1, xmss_pk=self.slave.pk, master_addr=self.alice.address) tx.sign(self.slave) ots_key = self.slave.ots_index - 1 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.slave.address, ots_key) state_container.paginated_bitfield.put_addresses_bitfield(None) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.slave.address, ots_key)) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, self.alice.ots_index)) tx._apply_state_changes_for_PK(state_container) tx._revert_state_changes_for_PK(state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.slave.address, ots_key)) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, self.alice.ots_index))
def test_apply_token_txn(self): """ Alice creates a token. Obviously, she gives herself some of this token. But she also gives Bob some tokens too. """ initial_balances = [qrl_pb2.AddressAmount(address=self.alice.address, amount=1000), qrl_pb2.AddressAmount(address=self.bob.address, amount=2000)] self.params["initial_balances"] = initial_balances tx = TokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) # According to the State, Alice has 100 coins, and Bob has 0 coins. # After applying the Transaction, Alice and Bob should have 1000 tokens, and Alice's balance should be 99. # AddressState.transaction_hashes now also reference the TokenTransaction that created the Tokens. self.assertFalse(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.alice.address, 0)) tx.apply(self.state, state_container) self.assertTrue(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.alice.address, 0)) self.assertEqual(addresses_state[self.alice.address].balance, 99) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual(2, len(state_container.paginated_tx_hash.key_value)) storage_key = state_container.paginated_tx_hash.generate_key(self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(self.bob.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(2, len(state_container.tokens.data)) self.assertIn((self.alice.address, tx.txhash), state_container.tokens.data) self.assertEqual(1000, state_container.tokens.data[(self.alice.address, tx.txhash)].balance) self.assertIn((self.bob.address, tx.txhash), state_container.tokens.data) self.assertEqual(2000, state_container.tokens.data[(self.bob.address, tx.txhash)].balance)
def test_apply_state_changes_for_PK_master_slave_XMSS(self, m_logger): """ If the TX was signed by a slave XMSS, the slave XMSS's AddressState should be updated (not the master's). :return: """ tx = TransferTransaction.create(addrs_to=[self.bob.address], amounts=[100], message_data=None, fee=1, xmss_pk=self.slave.pk, master_addr=self.alice.address) addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.slave.address: OptimizedAddressState.get_default(self.slave.address) } tx.sign(self.slave) ots_key = self.slave.ots_index - 1 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.slave.address, ots_key)) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, self.alice.ots_index)) tx._apply_state_changes_for_PK(state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.slave.address, ots_key)) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, self.alice.ots_index)) self.assertEqual(1, addresses_state[self.slave.address].nonce) self.assertEqual(0, addresses_state[self.alice.address].nonce)
def test_revert_token_txn_owner_not_in_address_state(self): """ In this case, Alice didn't give herself any tokens. How generous! She gave them all to Bob. But we want to revert this. """ initial_balances = [qrl_pb2.AddressAmount(address=self.bob.address, amount=1000)] self.params["initial_balances"] = initial_balances tx = TokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, 0) self.assertTrue(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.alice.address, 0)) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertFalse(state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse(self.alice.address, 0)) self.assertEqual(addresses_state[self.alice.address].balance, 100) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual(2, len(state_container.paginated_tx_hash.key_value)) storage_key = state_container.paginated_tx_hash.generate_key(self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(self.bob.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(1, len(state_container.tokens.data)) self.assertIn((self.bob.address, tx.txhash), state_container.tokens.data) self.assertEqual(0, state_container.tokens.data[(self.bob.address, tx.txhash)].balance)
def mock_new_state_container(self): addresses_state = {alice.address: self.alice_address_state} self.chain_manager.new_state_container.return_value = StateContainer( addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=5, total_coin_supply=0, current_dev_config=config.dev, write_access=False, my_db=None, batch=None)
def test_revert_transfer_txn_tx_sends_to_self(self, m_logger): """ Alice sent coins to herself, but she still lost the Transaction fee. Undo this. """ addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.bob.address: OptimizedAddressState.get_default(self.bob.address) } addresses_state[self.alice.address].pbdata.balance = 200 tx = TransferTransaction.create(addrs_to=[self.alice.address], amounts=[100], message_data=None, fee=1, xmss_pk=self.alice.pk) tx.sign(self.alice) ots_key = self.alice.ots_index - 1 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, ots_key) state_container.paginated_bitfield.put_addresses_bitfield(None) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.assertEqual(200, addresses_state[self.alice.address].balance) self.assertEqual(0, addresses_state[self.bob.address].balance)
def test_apply_transfer_txn_tx_sends_to_self(self, m_logger): """ If you send coins to yourself, you should only lose the fee for the Transaction. """ addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.bob.address: OptimizedAddressState.get_default(self.bob.address), self.slave.address: OptimizedAddressState.get_default(self.slave.address) } addresses_state[self.alice.address].pbdata.balance = 200 tx = TransferTransaction.create(addrs_to=[self.alice.address], amounts=[100], message_data=None, fee=1, xmss_pk=self.alice.pk) tx.sign(self.alice) ots_key = self.alice.ots_index - 1 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) tx.apply(self.state, state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.assertEqual(199, addresses_state[self.alice.address].balance) storage_key = state_container.paginated_tx_hash.generate_key( self.alice.address, 1) self.assertIn(tx.txhash, state_container.paginated_tx_hash.key_value[storage_key])
def test_validate_extended(self, m_logger): """ validate_extended() handles these parts of the validation: 1. Master/slave 2. balance, amount + fee from AddressState 3. OTS key reuse from AddressState :return: """ alice_address_state = OptimizedAddressState.get_default( self.alice.address) addresses_state = {self.alice.address: alice_address_state} alice_address_state.pbdata.balance = 200 self.tx.validate_slave = Mock(autospec=Transaction.validate_slave, return_value=True) self.tx.sign(self.alice) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) result = self.tx.validate_all(state_container) self.assertTrue(result) # Suppose there was ots key reuse. The function should then return false. state_container.paginated_bitfield.set_ots_key(addresses_state, self.tx.addr_from, self.tx.ots_key) result = self.tx.validate_all(state_container) self.assertFalse(result) # Suppose the address doesn't have enough coins. alice_address_state.pbdata.balance = 99 state_container.paginated_bitfield.set_ots_key(addresses_state, self.tx.addr_from, self.tx.ots_key) result = self.tx.validate_all(state_container) self.assertFalse(result)
def test_apply_transfer_txn_multi_send(self, m_logger): """ Test that apply_state_changes() also works with multiple recipients. """ addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.bob.address: OptimizedAddressState.get_default(self.bob.address), self.slave.address: OptimizedAddressState.get_default(self.slave.address) } addresses_state[self.alice.address].pbdata.balance = 200 tx_multisend = TransferTransaction.create( addrs_to=[self.bob.address, self.slave.address], amounts=[20, 20], message_data=None, fee=1, xmss_pk=self.alice.pk) tx_multisend.sign(self.alice) ots_key = self.alice.ots_index - 1 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) tx_multisend.apply(self.state, state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.assertEqual(159, addresses_state[self.alice.address].balance) self.assertEqual(20, addresses_state[self.bob.address].balance) self.assertEqual(20, addresses_state[self.slave.address].balance)
def test_validate_extended(self, m_logger): """ CoinBase validate_extended() checks for 1. valid coinbase address (the coinbase address must be config.dev.coinbase_address) 2. valid addr_to then calls _validate_custom() """ tx = CoinBase.create(config.dev, self.amount, self.alice.address, self.mock_blockheader.block_number) tx._data.master_addr = self.alice.address addresses_state = { config.dev.coinbase_address: OptimizedAddressState.get_default(config.dev.coinbase_address), self.alice.address: OptimizedAddressState.get_default(self.alice.address), } addresses_state[config.dev.coinbase_address].pbdata.balance = 1000000 state_container = StateContainer( addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=self.mock_blockheader.block_number, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) result = tx._validate_extended(state_container) self.assertFalse(result) tx._data.master_addr = config.dev.coinbase_address with patch('qrl.core.txs.CoinBase.CoinBase.addr_to', new_callable=PropertyMock) as m_addr_to: m_addr_to.return_value = b'Fake Address' result = tx._validate_extended(state_container) self.assertFalse(result) result = tx._validate_extended(state_container) self.assertTrue(result)
def test_validate_extended(self): """ CoinBase validate_extended() checks for 1. valid coinbase address (the coinbase address must be config.dev.coinbase_address) 2. valid addr_to then calls _validate_custom() """ tx = MultiSigCreate.create(signatories=self.signatories, weights=self.weights, threshold=self.threshold, fee=5, xmss_pk=self.alice.pk) tx.sign(self.alice) alice_address_state = OptimizedAddressState.get_default( address=self.alice.address) alice_address_state.pbdata.balance = 5 addresses_state = { self.alice.address: alice_address_state, } state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=10, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=None, batch=None) result = tx._validate_extended(state_container) self.assertTrue(result) alice_address_state.pbdata.balance = 4 result = tx._validate_extended(state_container) self.assertFalse( result) # False due to insufficient balance to pay the txn fee alice_address_state.pbdata.balance = 6 result = tx._validate_extended(state_container) self.assertTrue(result)
def __init__(self, addresses_state: dict, tokens: Indexer, slaves: Indexer, lattice_pk: Indexer, multi_sig_spend_txs: dict, votes_stats: dict, block_number: int, total_coin_supply: int, current_dev_config: DevConfig, write_access: bool, my_db: db, batch): self.delete_keys = set() self.paginated_bitfield = PaginatedBitfield(write_access, my_db) self.paginated_tx_hash = PaginatedData(b'p_tx_hash', write_access, my_db) self.paginated_tokens_hash = PaginatedData(b'p_tokens', write_access, my_db) self.paginated_slaves_hash = PaginatedData(b'p_slaves', write_access, my_db) self.paginated_lattice_pk = PaginatedData(b'p_lattice_pk', write_access, my_db) self.paginated_multisig_address = PaginatedData( b'p_multisig_address', write_access, my_db) self.paginated_multi_sig_spend = PaginatedData(b'p_multi_sig_spend', write_access, my_db) self.paginated_inbox_message = PaginatedData(b'p_inbox_message', write_access, my_db) self.addresses_state = addresses_state self.tokens = tokens self.slaves = slaves self.lattice_pk = lattice_pk self.multi_sig_spend_txs = multi_sig_spend_txs self.votes_stats = votes_stats self.block_number = block_number # Block number that is being processed self.block_reward = int(block_reward(block_number, current_dev_config)) self.batch = batch self.db = my_db self.current_dev_config = current_dev_config # Keeps track of last update so that it can be reverted self.last_addresses_state = dict() self.last_tokens = Indexer(b'token', self.db) self.last_slaves = Indexer(b'slave', self.db) self.last_lattice_pk = Indexer(b'lattice_pk', self.db) self.last_multi_sig_spend_txs = dict() self.last_votes_stats = dict() self._total_coin_supply = total_coin_supply # TODO: Coinbase transaction of current block is not included
def revert_update(self) -> bool: if not self._revert_copy_key_value(self.last_addresses_state, self.addresses_state, True): logger.error( "Error reverting last_addresses_state from state_container") return False self.last_addresses_state = dict() if not self._revert_copy_key_value(self.last_tokens.data, self.tokens.data, True): logger.error("Error reverting last_tokens from state_container") return False self.last_tokens = Indexer(b'token', self.db) if not self._revert_copy_key_value(self.last_slaves.data, self.slaves.data, True): logger.error("Error reverting last_slaves from state_container") return False self.last_slaves = Indexer(b'slave', self.db) if not self._revert_copy_key_value(self.last_lattice_pk.data, self.lattice_pk.data, True): logger.error( "Error reverting last_lattice_pk from state_container") return False self.last_lattice_pk = Indexer(b'lattice_pk', self.db) if not self._revert_copy_key_value(self.last_multi_sig_spend_txs, self.multi_sig_spend_txs, True): logger.error( "Error reverting last_multi_sig_spend_txs from state_container" ) return False self.last_multi_sig_spend_txs = dict() if not self._revert_copy_key_value(self.last_votes_stats, self.votes_stats, True): logger.error( "Error reverting last_votes_stats from state_container") return False self.last_votes_stats = dict() return True
def test_validate_all(self): tx = MultiSigCreate.create(signatories=self.signatories, weights=self.weights, threshold=self.threshold, fee=5, xmss_pk=self.random.pk, master_addr=self.alice.address) tx.sign(self.random) tx.pbdata.nonce = 1 alice_address_state = OptimizedAddressState.get_default( address=self.alice.address) alice_address_state.pbdata.balance = 5 random_address_state = OptimizedAddressState.get_default( address=self.random.address) addresses_state = { self.alice.address: alice_address_state, self.random.address: random_address_state, } slaves = Indexer(b'slave', None) slaves.data[(self.alice.address, self.random.pk)] = SlaveMetadata(access_type=0) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=slaves, lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=10, total_coin_supply=100, current_dev_config=config.dev, write_access=False, my_db=self.state._db, batch=None) result = tx.validate_all(state_container) self.assertTrue(result) tx.pbdata.nonce = 2 result = tx.validate_all(state_container) self.assertFalse(result) # False as nonce is invalid
def test_revert_transfer_token_txn_send_tokens_to_self(self): """ Alice has 1000 tokens and 100 QRL. She sends some tokens to herself. Can we undo this? """ self.params["addrs_to"] = [self.alice.address] tx = TransferTokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 tokens = Indexer(b'token', None) tokens.data[(self.alice.address, tx.token_txhash)] = TokenBalance(balance=100) state_container = StateContainer(addresses_state=addresses_state, tokens=tokens, slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, 0) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) self.assertEqual(addresses_state[self.alice.address].balance, 100) # Unfortunately importing mock.call results in some sort of ValueError so I can't check the arguments. self.assertEqual( tokens.data[(self.alice.address, tx.token_txhash)].balance, 100)
def test_apply_state_changes_owner_not_in_address_state(self): """ In this case, Alice didn't give herself any tokens. How generous! She gave them all to Bob. """ initial_balances = [qrl_pb2.AddressAmount(address=self.bob.address, amount=1000)] self.params["initial_balances"] = initial_balances tx = TokenTransaction.create(**self.params) tx.sign(self.alice) # Signing the TX also generates the txhash, which we need to generate the AddressState properly. addresses_state = dict(self.addresses_state) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 99) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual(2, len(state_container.paginated_tx_hash.key_value)) storage_key = state_container.paginated_tx_hash.generate_key(self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) storage_key = state_container.paginated_tx_hash.generate_key(self.bob.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual([tx.txhash], state_container.paginated_tx_hash.key_value[storage_key]) self.assertEqual(1, len(state_container.tokens.data)) self.assertIn((self.bob.address, tx.txhash), state_container.tokens.data) self.assertEqual(1000, state_container.tokens.data[(self.bob.address, tx.txhash)].balance)
def test_apply_transfer_txn(self, m_logger): """ apply_state_changes() is the part that actually updates everybody's balances. Then it forwards the addresses_state to _apply_state_changes_for_PK(), which updates everybody's addresses's nonce, OTS key index, and associated TX hashes If there is no AddressState for a particular Address, nothing is done. """ self.tx.sign(self.alice) ots_key = self.alice.ots_index - 1 addresses_state = { self.alice.address: OptimizedAddressState.get_default(self.alice.address), self.bob.address: OptimizedAddressState.get_default(self.bob.address), self.slave.address: OptimizedAddressState.get_default(self.slave.address) } addresses_state[self.alice.address].pbdata.balance = 200 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) self.tx.apply(self.state, state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, ots_key)) # Now Alice should have 99 coins left (200 - 100 - 1) and Bob should have 100 coins. self.assertEqual(99, addresses_state[self.alice.address].balance) self.assertEqual(100, addresses_state[self.bob.address].balance)
def test_apply_transfer_token_txn(self): """ Alice has 1000 tokens and 100 QRL, Bob has none. Alice sends some tokens to Bob. """ tx = TransferTokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) tokens = Indexer(b'token', None) tokens.data[(self.alice.address, tx.token_txhash)] = TokenBalance(balance=150) tokens.data[(self.bob.address, tx.token_txhash)] = TokenBalance(balance=0) state_container = StateContainer(addresses_state=addresses_state, tokens=tokens, slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) tx.apply(self.state, state_container) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) self.assertEqual(addresses_state[self.alice.address].balance, 99) self.assertEqual(addresses_state[self.bob.address].balance, 0) self.assertEqual( tokens.data[(self.alice.address, tx.token_txhash)].balance, 50) self.assertEqual( tokens.data[(self.bob.address, tx.token_txhash)].balance, 100)
def test_revert_transfer_token_txn_empty_addresses_state(self): """ If we didn't have any AddressStates for the addresses involved in this test, do nothing """ tx = TransferTokenTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) tokens = Indexer(b'token', None) tokens.data[(self.alice.address, tx.token_txhash)] = TokenBalance(balance=100) state_container = StateContainer(addresses_state=addresses_state, tokens=tokens, slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) state_container.paginated_bitfield.set_ots_key(addresses_state, self.alice.address, 0) self.assertTrue( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertFalse( state_container.paginated_bitfield.load_bitfield_and_ots_key_reuse( self.alice.address, 0)) self.assertEqual( 100, tokens.data[(self.alice.address, tx.token_txhash)].balance) self.assertEqual( 0, tokens.data[(self.bob.address, tx.token_txhash)].balance)
def test_validate_slave_signing_xmss_state_has_no_slave_permissions_in_state(self, m_logger): bob = get_bob_xmss() # Let's say Alice is Bob's master. self.params["master_addr"] = self.alice.address self.params["xmss_pk"] = bob.pk # We need to add extra data to the mock AddressState. tx = MessageTransaction.create(**self.params) tx.sign(self.alice) state_container = StateContainer(addresses_state=dict(), tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=1000, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) result = tx.validate_slave(state_container) self.assertFalse(result)