diff --git a/tests/integration/test_federation.py b/tests/integration/test_federation.py
new file mode 100644
index 00000000..c18c65de
--- /dev/null
+++ b/tests/integration/test_federation.py
@@ -0,0 +1,215 @@
+from copy import deepcopy
+import pytest
+import random
+
+import bigchaindb
+from bigchaindb.core import Bigchain
+from contextlib import contextmanager
+from bigchaindb.common.crypto import generate_key_pair
+from tests.pipelines.stepping import create_stepper
+
+
+################################################################################
+# Test setup code
+
+
+@contextmanager
+def federation(n):
+    """
+    Return a list of Bigchain objects and pipeline steppers to represent
+    a BigchainDB federation
+    """
+    keys = [generate_key_pair() for _ in range(n)]
+    config_orig = bigchaindb.config
+
+    @contextmanager
+    def make_nodes(i):
+        nonlocal keys
+        if i == 0:
+            yield []
+        else:
+            config = deepcopy(config_orig)
+            keys = [keys[-1]] + keys[:-1]
+            config['keypair']['private'] = keys[0][0]
+            config['keypair']['public'] = keys[0][1]
+            config['keyring'] = list(list(zip(*keys[1:]))[1])
+            bigchaindb.config = config
+            stepper = create_stepper()
+            with stepper.start():
+                node = (Bigchain(), stepper)
+                with make_nodes(i-1) as rest:
+                    yield [node] + rest
+
+    with make_nodes(n) as steppers:
+        bigchaindb.config = config_orig
+        yield zip(*steppers)
+
+
+@pytest.fixture
+def federation_3():
+    with federation(3) as f:
+        yield f
+
+
+def process_tx(steps):
+    steps.block_changefeed(timeout=1)
+    if steps.block_filter_tx():
+        steps.block_validate_tx()
+        steps.block_create(timeout=True)
+        steps.block_write()
+        steps.block_delete_tx()
+
+
+def input_single_create(b):
+    from bigchaindb.common.transaction import Transaction
+    metadata = {'r': random.random()}
+    tx = Transaction.create([b.me], [([b.me], 1)], metadata).sign([b.me_private])
+    b.write_transaction(tx)
+    return tx
+
+
+def process_vote(steps, result=None):
+    steps.vote_changefeed()
+    steps.vote_validate_block()
+    steps.vote_ungroup()
+    steps.vote_validate_tx()
+    if result is not None:
+        steps.queues['vote_vote'][0][0] = result
+    vote = steps.vote_vote()
+    steps.vote_write_vote()
+    return vote
+
+
+################################################################################
+# Tests here on down
+
+
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_valid(federation_3):
+    [bx, (s0, s1, s2)] = federation_3
+    tx = input_single_create(bx[0])
+    process_tx(s0)
+    process_tx(s1)
+    process_tx(s2)
+    process_vote(s2, False)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    process_vote(s0, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    process_vote(s1, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'valid'
+
+
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_invalid(federation_3):
+    [bx, (s0, s1, s2)] = federation_3
+    tx = input_single_create(bx[0])
+    process_tx(s0)
+    process_tx(s1)
+    process_tx(s2)
+    process_vote(s1, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    process_vote(s2, False)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    process_vote(s0, False)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] is None
+
+
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_disagree_prev_block(federation_3):
+    [bx, (s0, s1, s2)] = federation_3
+    tx = input_single_create(bx[0])
+    process_tx(s0)
+    process_tx(s1)
+    process_tx(s2)
+    process_vote(s0, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    s1.vote.last_voted_id = '5' * 64
+    process_vote(s1, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+    s2.vote.last_voted_id = '6' * 64
+    process_vote(s2, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] is None
+
+
+@pytest.mark.skip()  # TODO: wait for #1309
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_dupe_vote(federation_3):
+    from bigchaindb.exceptions import CriticalDuplicateVote
+    [bx, (s0, s1, s2)] = federation_3
+    tx = input_single_create(bx[0])
+    process_tx(s0)
+    process_tx(s1)
+    process_tx(s2)
+    vote = process_vote(s0, True)
+    # Drop the unique index and write the vote again
+    bx[0].connection.db.votes.drop_index('block_and_voter')
+    s0.queues['vote_write_vote'].append([vote])
+    s0.vote_write_vote()
+    for i in range(3):
+        with pytest.raises(CriticalDuplicateVote):
+            bx[i].get_transaction(tx.id, True)[1]
+
+
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_sybill(federation_3):
+    [bx, (s0, s1, s2)] = federation_3
+    tx = input_single_create(bx[0])
+    process_tx(s0)
+    process_tx(s1)
+    process_tx(s2)
+    # What we need is some votes from unknown nodes!
+    for s in [s0, s1, s2]:
+        s.vote.bigchain.me_private = generate_key_pair()[0]
+    process_vote(s0, True)
+    process_vote(s1, True)
+    process_vote(s2, True)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'undecided'
+
+
+@pytest.mark.skip()
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_dos(federation_3):
+    """
+    https://github.com/bigchaindb/bigchaindb/issues/1314
+    Test that a node cannot block another node's opportunity to vote
+    on a block by writing an incorrectly signed vote
+    """
+    raise NotImplementedError()
+
+
+@pytest.mark.skip('Revisit when we have block election status cache')
+@pytest.mark.bdb
+@pytest.mark.genesis
+def test_elect_bad_block_voters_list(federation_3):
+    """
+    See https://github.com/bigchaindb/bigchaindb/issues/1224
+    """
+    [bx, (s0, s1, s2)] = federation_3
+    b = s0.block.bigchain
+    # First remove other nodes from node 0 so that it self assigns the tx
+    b.nodes_except_me = []
+    tx = input_single_create(b)
+    # Now create a block voters list which will not match other keyrings
+    b.nodes_except_me = [bx[1].me]
+    process_tx(s0)
+    process_vote(s0)
+    process_vote(s1)
+    process_vote(s2)
+    for i in range(3):
+        assert bx[i].get_transaction(tx.id, True)[1] == 'invalid'
diff --git a/tests/pipelines/stepping.py b/tests/pipelines/stepping.py
index 1a9d3a69..36f68a6a 100644
--- a/tests/pipelines/stepping.py
+++ b/tests/pipelines/stepping.py
@@ -163,6 +163,8 @@ def _update_stepper(stepper, prefix, pipeline):
         n1 = (nodes + [None])[i+1]
         f = stepper.add_input if i == 0 else stepper.add_stage
         f(prefix, n0, n1)
+    # Expose pipeline state
+    setattr(stepper, prefix, nodes[-1].target.__self__)
 
 
 def create_stepper():