Skip to content
This repository has been archived by the owner on Oct 20, 2020. It is now read-only.

Change the state machine used for processing new blocks #20

Merged
merged 2 commits into from Oct 6, 2020

Conversation

abacabadabacaba
Copy link
Contributor

Now a newly added block that is not yet trusted is stored in untrustedHead. After the challenge period is over, it can be moved to head. Fixes #13, #15.

BlockInfo public backupHead;

// The most recently added block. May still be in its challenge period, so should not be trusted.
BlockInfo public untrustedHead;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to make a difference between head and untrustedHead!

Copy link
Member

@ailisp ailisp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, need bump version in package.json if we want to use this immediately. verify_near_headers test failed because it simply try to submit all blocks during a period, so refused by "not sufficiently new" check, either change test or remove some blocks for now

@abacabadabacaba
Copy link
Contributor Author

I fixed verify_near_headers, but it still won't pass cli tests because there is new constructor parameter.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic makes contract more complex by adding an additional public method. Instead we can make head and backupHead private and add an additional method getTrustedHead() which returns either head or backupHead depending on whether validAfter has passed.

@abacabadabacaba
Copy link
Contributor Author

This logic makes contract more complex by adding an additional public method. Instead we can make head and backupHead private and add an additional method getTrustedHead() which returns either head or backupHead depending on whether validAfter has passed.

I think that the logic is now less complex. There is head which is trusted. A new method is needed to move untrustedHead to head when the challenge period is over without pushing a new block. This also makes this block available to blockHashes and blockMerkleRoots. It is possible to add an extra method, but it won't make things less complex, because there should be still a way to commit a block so that NearProver can use it (it uses blockMerkleRoots).

Or I can just make commitBlock internal, so a block will only become available when another block is submitted on top of it.

Overall, this code needs a lot of refactoring, but for now I try not to change it too much.

@MaksymZavershynskyi
Copy link
Contributor

This also makes this block available to blockHashes and blockMerkleRoots. It is possible to add an extra method, but it won't make things less complex, because there should be still a way to commit a block so that NearProver can use it (it uses blockMerkleRoots).

We could add trustedMerkleRoots method that filter the last merkle root if the header has not passed the challenge period yet. By adding commitBlock we are delegating complexity to the contract user who now need to call an extra method before being able to use this contract.

Or I can just make commitBlock internal, so a block will only become available when another block is submitted on top of it.

This will make header that passed challenge period not useful until the next header is submitted. Ideally, we want header to be useful as soon as it passes the challenge period.

@abacabadabacaba
Copy link
Contributor Author

abacabadabacaba commented Sep 12, 2020

We could add trustedMerkleRoots method that filter the last merkle root if the header has not passed the challenge period yet.

That's not a good API design. "Trusted" should be the default (and indeed the only) option.

Anyway, I changed the API to return the last block as soon as the challenge period is over.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. There are some suggestions to add comments. We also need to add PR for rainbow-bridge-cli and rainbow-bridge-lib to make the tests pass. Sorry for the delay.

nearbridge/contracts/NearBridge.sol Outdated Show resolved Hide resolved
nearbridge/contracts/NearBridge.sol Outdated Show resolved Hide resolved
nearbridge/contracts/NearBridge.sol Outdated Show resolved Hide resolved
// Address of the account which submitted the last block.
address lastSubmitter;
// End of challenge period, or zero if there is no block to be challenged.
uint lastValidAt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment saying something like: "when it is zero untrustedHead, untrustedHeadIsFromNextEpoch, untrustedNextBlockProducers should be considered to be not set".

nearbridge/contracts/NearBridge.sol Show resolved Hide resolved
@abacabadabacaba
Copy link
Contributor Author

@nearmax I addressed the comments and also fixed another bug, is everything OK now? Will make a PR for near-bridge-cli and -lib a bit later. I can't merge this PR because of CI.

@abacabadabacaba
Copy link
Contributor Author

Created pull requests: Near-One/rainbow-bridge#348, near/rainbow-bridge-lib#14.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else {
// The new block is from the next epoch.
_checkBp(nearBlock, nextBlockProducers);
revert("NearBridge: Epoch id of the block is not valid");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this the additional bug that you fixed? It is hard to see because of the forced push.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug that I fixed was that I added a check to checkBlockProducerSignatureInHead that signatureIndex is valid.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about signatureIndex < untrustedBlockProducers.bpsLength check? Because it seems !untrustedApprovals[signatureIndex].none check does not fix vulnerabilities.

Copy link
Member

@ailisp ailisp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@@ -15,6 +15,7 @@ do
# Get contract name without extension and without directories.
contract_name="${filename%.*}"
node_modules/.bin/truffle-flattener "./contracts/${contract_name}.sol" > "dist/${contract_name}.full.sol"
sed -i '/^\/\/ SPDX-License-Identifier:/d' "dist/${contract_name}.full.sol"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a comment explaining what is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was because of NomicFoundation/truffle-flattener#55. Will add a comment.

@@ -75,26 +82,27 @@ contract NearBridge is INearBridge {
bytes32 blockHash
);

constructor(Ed25519 ed, uint256 _lockEthAmount, uint256 _lockDuration) public {
constructor(Ed25519 ed, uint256 _lockEthAmount, uint256 _lockDuration, uint256 _replaceDuration) public {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR might be a good opportunity to make sure we follow Solidity style guide: https://solidity.readthedocs.io/en/v0.6.0/style-guide.html#avoiding-naming-collisions

balanceOf[msg.sender] = balanceOf[msg.sender].sub(lockEthAmount);
msg.sender.transfer(lockEthAmount);
}

function challenge(address payable receiver, uint256 signatureIndex) public {
require(block.timestamp < head.validAfter, "Lock period already passed");
function challenge(address payable receiver, uint256 signatureIndex) override public {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU override is only useful when contract inherits from another contract, not from an interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Solidity 0.6, override is mandatory when inheriting from an interface. Without it, it just won't compile.

res.currentHeight = head.height;
res.nextTimestamp = untrustedHead.timestamp;
res.nextValidAt = lastValidAt;
res.numBlockProducers =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unclear why do we not return numBlockProducers when there is an untrusted head.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do return the number of block producers when there is an untrusted head. This number is used by the watchdog to determine the number of signatures to check.

function addLightClientBlock(bytes memory data) public payable {
struct BridgeState {
uint currentHeight;
uint nextTimestamp;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please add comments explaining each field?

} else {
// The new block is from the next epoch.
_checkBp(nearBlock, nextBlockProducers);
revert("NearBridge: Epoch id of the block is not valid");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about signatureIndex < untrustedBlockProducers.bpsLength check? Because it seems !untrustedApprovals[signatureIndex].none check does not fix vulnerabilities.

}

// 6. If next_bps is not none, sha256(borsh(next_bps)) corresponds to the next_bp_hash in inner_lite.
if (!nearBlock.next_bps.none) {
// Check that the new block is signed by more than 2/3 of the validators.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The numbers in the comments were useful because they corresponded to the spec: https://nomicon.io/ChainSpec/LightClient.html

…. Now a newly added block that is not yet trusted is stored in untrustedHead. After the challenge period is over, it can be moved to head. Fixes #13, #15.
Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@MaksymZavershynskyi
Copy link
Contributor

I have tested this change locally, so I am going to merge it and publish the package.

@MaksymZavershynskyi MaksymZavershynskyi merged commit d89751f into master Oct 6, 2020
@abacabadabacaba abacabadabacaba deleted the fix-bridge-state-machine branch October 6, 2020 10:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Critical] TokenLocker allows to use header that did not pass challenge period
3 participants