Adjust ABCI handlers to shift migration height.

And abort if the current chain is not synced.
This commit is contained in:
Lev Berman 2018-08-30 17:31:59 +02:00
parent b5fe2b15ce
commit cef559cdb0
2 changed files with 131 additions and 14 deletions

View File

@ -49,6 +49,19 @@ class App(BaseApplication):
self.validators = None self.validators = None
self.new_height = None self.new_height = None
def log_abci_migration_error(self, chain_id, validators):
logger.error(f'An ABCI chain migration is in process. ' +
'Download the new ABCI client and configure it with ' +
'chain_id={chain_id} and validators={validators}.')
def abort_if_abci_chain_is_not_synced(self, chain):
if chain is None or chain['is_synced']:
return
validators = self.bigchaindb.get_validators()
self.log_abci_migration_error(chain['chain_id'], validators)
sys.exit(1)
def init_chain(self, genesis): def init_chain(self, genesis):
"""Initialize chain upon genesis or a migration""" """Initialize chain upon genesis or a migration"""
@ -60,17 +73,14 @@ class App(BaseApplication):
chain_id = known_chain['chain_id'] chain_id = known_chain['chain_id']
if known_chain['is_synced']: if known_chain['is_synced']:
msg = f'Ignoring the InitChain ABCI request ({genesis}) - ' + \ msg = f'Got invalid InitChain ABCI request ({genesis}) - ' + \
'the chain {chain_id} is already synced.' 'the chain {chain_id} is already synced.'
logger.error(msg) logger.error(msg)
sys.exit(1) sys.exit(1)
if chain_id != genesis.chain_id: if chain_id != genesis.chain_id:
msg = f'Got mismatching chain ID in the InitChain ' + \ validators = self.bigchaindb.get_validators()
'ABCI request - you need to migrate the ABCI client ' + \ self.log_abci_migration_error(chain_id, validators)
'and set new chain ID: {chain_id}.'
logger.error(msg)
sys.exit(1) sys.exit(1)
# set migration values for app hash and height # set migration values for app hash and height
@ -83,10 +93,8 @@ class App(BaseApplication):
for v in genesis.validators] for v in genesis.validators]
if known_validators and known_validators != validator_set: if known_validators and known_validators != validator_set:
msg = f'Got mismatching validator set in the InitChain ' + \ self.log_abci_migration_error(known_chain['chain_id'],
'ABCI request - you need to migrate the ABCI client ' + \ known_validators)
'and set new validator set: {known_validators}.'
logger.error(msg)
sys.exit(1) sys.exit(1)
block = Block(app_hash=app_hash, height=height, transactions=[]) block = Block(app_hash=app_hash, height=height, transactions=[])
@ -99,10 +107,15 @@ class App(BaseApplication):
def info(self, request): def info(self, request):
"""Return height of the latest committed block.""" """Return height of the latest committed block."""
chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
r = ResponseInfo() r = ResponseInfo()
block = self.bigchaindb.get_latest_block() block = self.bigchaindb.get_latest_block()
if block: if block:
r.last_block_height = block['height'] chain_shift = 0 if chain is None else chain['height']
r.last_block_height = block['height'] - chain_shift
r.last_block_app_hash = block['app_hash'].encode('utf-8') r.last_block_app_hash = block['app_hash'].encode('utf-8')
else: else:
r.last_block_height = 0 r.last_block_height = 0
@ -117,6 +130,9 @@ class App(BaseApplication):
raw_tx: a raw string (in bytes) transaction. raw_tx: a raw string (in bytes) transaction.
""" """
chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
logger.benchmark('CHECK_TX_INIT') logger.benchmark('CHECK_TX_INIT')
logger.debug('check_tx: %s', raw_transaction) logger.debug('check_tx: %s', raw_transaction)
transaction = decode_transaction(raw_transaction) transaction = decode_transaction(raw_transaction)
@ -135,8 +151,12 @@ class App(BaseApplication):
req_begin_block: block object which contains block header req_begin_block: block object which contains block header
and block hash. and block hash.
""" """
chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
chain_shift = 0 if chain is None else chain['height']
logger.benchmark('BEGIN BLOCK, height:%s, num_txs:%s', logger.benchmark('BEGIN BLOCK, height:%s, num_txs:%s',
req_begin_block.header.height, req_begin_block.header.height + chain_shift,
req_begin_block.header.num_txs) req_begin_block.header.num_txs)
self.block_txn_ids = [] self.block_txn_ids = []
@ -149,6 +169,10 @@ class App(BaseApplication):
Args: Args:
raw_tx: a raw string (in bytes) transaction. raw_tx: a raw string (in bytes) transaction.
""" """
chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
logger.debug('deliver_tx: %s', raw_transaction) logger.debug('deliver_tx: %s', raw_transaction)
transaction = self.bigchaindb.is_valid_transaction( transaction = self.bigchaindb.is_valid_transaction(
decode_transaction(raw_transaction), self.block_transactions) decode_transaction(raw_transaction), self.block_transactions)
@ -170,7 +194,12 @@ class App(BaseApplication):
height (int): new height of the chain. height (int): new height of the chain.
""" """
height = request_end_block.height chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
chain_shift = 0 if chain is None else chain['height']
height = request_end_block.height + chain_shift
self.new_height = height self.new_height = height
block_txn_hash = calculate_hash(self.block_txn_ids) block_txn_hash = calculate_hash(self.block_txn_ids)
block = self.bigchaindb.get_latest_block() block = self.bigchaindb.get_latest_block()
@ -198,6 +227,9 @@ class App(BaseApplication):
def commit(self): def commit(self):
"""Store the new height and along with block hash.""" """Store the new height and along with block hash."""
chain = self.bigchaindb.get_latest_abci_chain()
self.abort_if_abci_chain_is_not_synced(chain)
data = self.block_txn_hash.encode('utf-8') data = self.block_txn_hash.encode('utf-8')
# register a new block only when new transactions are received # register a new block only when new transactions are received

View File

@ -11,6 +11,7 @@ from abci.types_pb2 import (
PubKey, PubKey,
ResponseInitChain, ResponseInitChain,
RequestInitChain, RequestInitChain,
RequestInfo,
RequestBeginBlock, RequestBeginBlock,
RequestEndBlock, RequestEndBlock,
Validator, Validator,
@ -166,6 +167,39 @@ def test_init_chain_recognizes_new_chain_after_migration(b):
} }
def test_info(b):
r = RequestInfo()
app = App(b)
res = app.info(r)
assert res.last_block_height == 0
assert res.last_block_app_hash == b''
b.store_block(Block(app_hash='1', height=1, transactions=[])._asdict())
res = app.info(r)
assert res.last_block_height == 1
assert res.last_block_app_hash == b'1'
# simulate a migration and assert the height is shifted
b.store_abci_chain(2, 'chain-XYZ')
b.store_block(Block(app_hash='2', height=2, transactions=[])._asdict())
res = app.info(r)
assert res.last_block_height == 0
assert res.last_block_app_hash == b'2'
b.store_block(Block(app_hash='3', height=3, transactions=[])._asdict())
res = app.info(r)
assert res.last_block_height == 1
assert res.last_block_app_hash == b'3'
# it's always the latest migration that is taken into account
b.store_abci_chain(4, 'chain-XYZ-new')
b.store_block(Block(app_hash='4', height=4, transactions=[])._asdict())
res = app.info(r)
assert res.last_block_height == 0
assert res.last_block_app_hash == b'4'
def test_check_tx__signed_create_is_ok(b): def test_check_tx__signed_create_is_ok(b):
from bigchaindb import App from bigchaindb import App
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
@ -199,7 +233,6 @@ def test_check_tx__unsigned_create_is_error(b):
assert result.code == CodeTypeError assert result.code == CodeTypeError
@pytest.mark.bdb
def test_deliver_tx__valid_create_updates_db(b, init_chain_request): def test_deliver_tx__valid_create_updates_db(b, init_chain_request):
from bigchaindb import App from bigchaindb import App
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
@ -367,6 +400,16 @@ def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request):
assert resp['height'] == 100 assert resp['height'] == 100
assert resp['transactions'] == [tx.id] assert resp['transactions'] == [tx.id]
# simulate a chain migration and assert the height is shifted
b.store_abci_chain(100, 'new-chain')
app.begin_block(begin_block)
app.deliver_tx(encode_tx_to_bytes(tx))
app.end_block(RequestEndBlock(height=1))
resp = query.get_pre_commit_state(b.connection, PRE_COMMIT_ID)
assert resp['commit_id'] == PRE_COMMIT_ID
assert resp['height'] == 101
assert resp['transactions'] == [tx.id]
def test_new_validator_set(b): def test_new_validator_set(b):
node1 = {'pub_key': {'type': 'ed25519', node1 = {'pub_key': {'type': 'ed25519',
@ -389,3 +432,45 @@ def test_new_validator_set(b):
'voting_power': u['power']}) 'voting_power': u['power']})
assert updated_validator_set == updated_validators assert updated_validator_set == updated_validators
def test_info_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).info(RequestInfo())
def test_check_tx_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).check_tx('some bytes')
def test_begin_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).info(RequestBeginBlock())
def test_deliver_tx_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).deliver_tx('some bytes')
def test_end_block_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).info(RequestEndBlock())
def test_commit_aborts_if_chain_is_not_synced(b):
b.store_abci_chain(0, 'chain-XYZ', False)
with pytest.raises(SystemExit):
App(b).commit()