aucoin/tests/test_validator_transaction.py
Casper V. Kristensen b7053ad014
Publish
2018-07-15 23:30:55 +02:00

199 lines
8.1 KiB
Python

import logging
import unittest
from unittest.mock import patch
from aucoin import dsa, util
from aucoin.block import Block
from aucoin.blockchain import Blockchain
from aucoin.database import session_scope
from aucoin.exceptions import InvalidTransactionException, OrphanException
from aucoin.mempool import Mempool
from aucoin.network import Network
from aucoin.transactions import Transaction, Input, Output, CoinbaseTransaction
from aucoin.validation import Validator
from aucoin.wallet import Wallet, public_bytes
from tests import helpers
from tests.helpers import mine
logging.basicConfig(level=logging.DEBUG)
class TestAddTransaction(unittest.TestCase):
def setUp(self):
self.blockchain = Blockchain(reset=True)
self.mempool = Mempool()
self.wallet = Wallet(self.blockchain, self.mempool)
self.network = Network(self.blockchain, self.mempool, max_peers=0)
self.validator = Validator(helpers.Core(), self.blockchain, self.mempool, self.network)
# add a block with an unspent transaction output we can reference
with session_scope() as session:
genesis = self.blockchain.genesis_block(session)
address = self.wallet.new_address()
self.private_key, self.public_key = self.wallet.keys[address]
self.block = Block(
hash_prev_block=genesis.hash,
target=genesis.target,
public_key=public_bytes(self.public_key),
transactions=[
CoinbaseTransaction(
address=address,
value=100,
block_height=1
)
]
)
self.validator.add_block(mine(self.block, self.private_key))
# the transaction which we will be modifying to provoke validation exceptions
self.transaction = Transaction(
inputs=[
Input(
prev_tx_hash=self.block.transactions[0].hash,
txout_index=0
)
],
outputs=[
Output(
value=50,
address=b"some_address"
)
]
)
self.wallet.sign(self.transaction, session)
def test_valid(self):
self.validator.add_transaction(self.transaction)
def test_reject_invalid_syntax(self):
self.transaction.inputs = []
self.assertRaisesRegex(InvalidTransactionException, "Input list is empty",
self.validator.add_transaction, self.transaction)
def test_reject_coinbase_transaction(self):
coinbase_transaction = CoinbaseTransaction(b"some_address")
self.assertRaisesRegex(InvalidTransactionException, "Standalone transaction cannot be coinbase",
self.validator.add_transaction, coinbase_transaction)
def test_reject_duplicate_in_blockchain(self):
with session_scope() as session:
block = Block(
hash_prev_block=self.block.hash,
target=self.block.target,
transactions=[
CoinbaseTransaction(
address=self.wallet.new_address(),
value=100
),
self.transaction
]
)
self.blockchain.add(block, session, main_branch=True)
self.assertRaisesRegex(InvalidTransactionException, "Transaction already exists in blockchain's main branch",
self.validator.add_transaction, self.transaction)
def test_reject_duplicate_mempool(self):
self.mempool[self.transaction.hash] = self.transaction
self.assertRaisesRegex(InvalidTransactionException, "Transaction already exists in mempool",
self.validator.add_transaction, self.transaction)
def test_reject_mempool_conflict(self):
# a transaction that spends the same input as self.transaction:
transaction = Transaction(
inputs=[
Input(
prev_tx_hash=self.block.transactions[0].hash,
txout_index=0
)
],
outputs=[
Output(
value=100,
address=b"another_address"
)
]
)
self.assertNotEqual(transaction.hash, self.transaction.hash)
self.assertEqual((self.transaction.inputs[0].prev_tx_hash, self.transaction.inputs[0].txout_index),
(transaction.inputs[0].prev_tx_hash, transaction.inputs[0].txout_index))
self.mempool[transaction.hash] = transaction
self.assertRaisesRegex(InvalidTransactionException, "Transaction conflicts with mempool: one or more input's referenced output is spent by another transaction in the mempool",
self.validator.add_transaction, self.transaction)
def test_reject_orphan(self):
input = Input(
prev_tx_hash=b"nonexistent_tx",
txout_index=123
)
self.transaction.inputs.append(input)
self.assertRaises(OrphanException, self.validator.add_transaction, self.transaction)
def test_reject_spent(self):
address = self.wallet.new_address()
private_key, public_key = self.wallet.keys[address]
block = Block(
hash_prev_block=self.block.hash,
target=self.block.target,
public_key=public_bytes(public_key),
transactions=[
CoinbaseTransaction(
address=address,
value=100,
block_height=2
)
] + [self.transaction]
)
self.validator.add_block(mine(block, private_key))
# a transaction that spends the same input as self.transaction:
transaction = Transaction(
inputs=[
Input(
prev_tx_hash=self.block.transactions[0].hash,
txout_index=0
)
],
outputs=[
Output(
value=20,
address=b"other_address"
)
]
)
self.assertRaisesRegex(InvalidTransactionException, "Referenced output already spent \(not in blockchain/mempool UTXO\) for one or more inputs",
self.validator.add_transaction, transaction)
def test_reject_negative_fee(self):
output = Output(
value=10000,
address=b"doesnt_matter"
)
self.transaction.outputs.append(output)
self.assertRaisesRegex(InvalidTransactionException, "Sum of input values < sum of output values \(negative fee\)",
self.validator.add_transaction, self.transaction)
def test_reject_too_low_fee(self):
with patch("aucoin.consensus.tx_min_fee", 51): # fee of self.transaction is 50
self.assertRaisesRegex(InvalidTransactionException, "Transaction fee too low",
self.validator.add_transaction, self.transaction)
def test_reject_invalid_signature(self):
self.transaction.inputs[0].signature = b"Wrong"
self.assertRaisesRegex(InvalidTransactionException, "Invalid signature for one or more inputs",
self.validator.add_transaction, self.transaction)
def test_reject_wrong_public_key(self):
with session_scope() as session:
for input, copy_hash in self.transaction.truncated_copies(self.blockchain, self.mempool, session):
keypair = dsa.generate_keypair() # generate new, wrong, keypair
input.public_key = public_bytes(keypair.public)
input.signature = dsa.sign(keypair.private, copy_hash)
self.assertRaisesRegex(InvalidTransactionException, "Public key doesn't match address of the output it is spending for one or more inputs",
self.validator.add_transaction, self.transaction)
if __name__ == '__main__':
unittest.main()