Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi AMB requests in one transaction #403

Merged
merged 47 commits into from
May 25, 2020
Merged

Multi AMB requests in one transaction #403

merged 47 commits into from
May 25, 2020

Conversation

k1rill-fedoseev
Copy link
Member

Resolves #307

@k1rill-fedoseev k1rill-fedoseev self-assigned this Apr 14, 2020
Copy link
Collaborator

@akolotov akolotov left a comment

Choose a reason for hiding this comment

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

Thanks for the improvement!

I have few remarks:

  1. Please add description headers to the methods you are changing.
  2. Since we have adopters already that developed and deployed their extensions for AMB (ETH-POA and ETH-xDAI) it seems that we need to start supporting two options to call requireToPassMessage from the mediator contracts.
  3. We need to think of the procedure to upgrade existing bridges - how the contracts could be upgraded as so the previous requests to pass messages cannot be replayed.

@k1rill-fedoseev
Copy link
Member Author

Regarding 2.

How we should distinguish these two functions? We cannot use both:

  • function requireToPassMessage(address, bytes, uint256) external returns (bytes32)
  • function requireToPassMessage(address, bytes, uint256) external

, they have the same signature.

The old version of mediators can successfully call new version of requireToPassMessage by simply ignoring its output. However, there are problems with two other functions: transactionHash() and failedMessageDataHash(bytes32 _txHash). If we remove them, old mediators logic will definitely break. In case our plan is to support old versions of mediators too, I propose to do the following:

  • transactionHash() can return the same value, as messageId() does. It won't be a real transaction hash anymore, but the mediator contract won't notice any difference. In case the mediator emits some events that include a returned value of transactionHash(), off-chain logic for handling such events should be updated.
  • failedMessageDataHash(bytes32) can be kept, as it is implemented now. And the AMB contract should continue to save dataHash for each failed message, even it is not needed anymore for the new mediators.

If we do so, then the old version of mediator still will be able to do something like:

function requestFailedMessageFix(bytes32 _txHash) external {
  require(!bridgeContract().messageCallStatus(_txHash));
  require(bridgeContract().failedMessageReceiver(_txHash) == address(this));
  require(bridgeContract().failedMessageSender(_txHash) == mediatorContractOnOtherSide());
  bytes32 dataHash = bridgeContract().failedMessageDataHash(_txHash);
  bytes4 methodSelector = this.fixFailedMessage.selector;
  bytes memory data = abi.encodeWithSelector(methodSelector, dataHash);
  bridgeContract().requireToPassMessage(mediatorContractOnOtherSide(), data, requestGasLimit());
}

And the new version mediator will be able to do something like:

function requestFailedMessageFix(bytes32 messageId) external {
  require(!bridgeContract().messageCallStatus(messageId));
  require(bridgeContract().failedMessageReceiver(messageId) == address(this));
  require(bridgeContract().failedMessageSender(messageId) == mediatorContractOnOtherSide());
  bytes4 methodSelector = this.fixFailedMessage.selector;
  bytes memory data = abi.encodeWithSelector(methodSelector, messageId);
  bridgeContract().requireToPassMessage(mediatorContractOnOtherSide(), data, requestGasLimit());
}

Regarding 3.

Replay attack won't be possible, since the message structure and processing logic didn't change.
For foreign contract, messageId has the same format and position in the encoded message, as txHash, so if someone will try to relay old already handled transaction, contract will treat txHash from this message as a messageId, but the require(!relayedMessages(messageId)); will revert the execution.
For home contract the situation is similar, if some validator will try to executeAffirmation on old message, the execution will be reverted, since the provided msgHash was already marked as processed.

contracts/interfaces/IAMB.sol Outdated Show resolved Hide resolved
contracts/libraries/Bytes.sol Show resolved Hide resolved
@akolotov
Copy link
Collaborator

@k1rill-fedoseev

I agree on the item 2. Thanks.

But for the item 3 we need to think more. Since the tx hash now is not the part of the message could it compromise security? In other words, if the message id is unique enough? E.g. it could be the same in different chains if the bridge contract was deployed from the same account with the same number of transaction (nonce). Is a similar property applicable for the tx hashes and they also could be non-unique?

@k1rill-fedoseev
Copy link
Member Author

This PR will eventually close #373 also. The existing bridge does not have any failed messages, so the support of previously used approach is not needed.

@k1rill-fedoseev k1rill-fedoseev linked an issue Apr 22, 2020 that may be closed by this pull request
@akolotov
Copy link
Collaborator

Let's prepare the changes for the oracle as well. We can trail the upgrade then on the bridge between Kovan and Sokol.

Copy link
Collaborator

@akolotov akolotov left a comment

Choose a reason for hiding this comment

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

As per offline discussion please consider to extend the message sending through the bridge as following:

version[4] -----|
address[20]     | message id
nonce[8]   -----|
chainId[32]
sender[20]
executor[20]
gas[4] 
dataType[1]
data[…]

Copy link
Collaborator

@akolotov akolotov left a comment

Choose a reason for hiding this comment

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

Another set of the comments

*/
function distributeFee(IMediatorFeeManager _feeManager, uint256 _fee, bytes32 _txHash) internal {
function distributeFee(IMediatorFeeManager _feeManager, uint256 _fee, bytes32 _messageId) internal {
// Right now, AMB bridge supports only one message per transaction.
// The receivers of the fee could try to send back the fees through the mediator,
// so here we add a lock to limit the number of messages that the mediator can send to the bridge,
// allowing a maximum of 1 message
enableMessagesRestriction();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Well, let's leave this as is now and address in a separate issue. We need to commit the changes for this new functionality in short time.

Copy link
Collaborator

@akolotov akolotov left a comment

Choose a reason for hiding this comment

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

Consider suggestions to use integers for the chain ids rather than set of bytes.

header = new bytes(79 + sourceChainIdLength + destinationChainIdLength);

assembly {
let ptr := add(header, mload(header)) // points to the last word of header
Copy link
Collaborator

Choose a reason for hiding this comment

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

Well, I think you need to add a comment before the assembly part saying that "in order to safe gas the header is packed from the end"

bytes32 mVer = MESSAGE_PACKING_VERSION;
uint256 nonce = _nonce();

bytes32 bridgeId = keccak256(abi.encodePacked(sourceChainId, address(this))) &
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would also add a comment here "it does not make sense to keep the bridge Id in a storage since the operation to read it will take 800 gas whereas the operations to get sha3 plus to combine the integer and the address into the byte blob will consume less"

mstore(ptr, destinationChainId)
mstore(sub(ptr, destinationChainIdLength), sourceChainId)

// can be ommitted for dataType 0x00, since header is already initialized with zeros
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// can be ommitted for dataType 0x00, since header is already initialized with zeros
// can be ommitted for dataType 0x00, since header is already initialized with zeros after the previous mstore operation

Copy link
Member Author

Choose a reason for hiding this comment

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

In case chainId is 32 bytes, I think that this zero will come from header = new bytes(79 + sourceChainIdLength + destinationChainIdLength); line. This bytes blob should be allocated from the free memory pointer at 0x40, so it will be pre-filled with zeros.

Am I right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure that it is pre-filled with zeros. Did you look at the decompiled solidity code to confirm this? Because, in general, the solidity considerations will work even for the next code to allocate a new set of bytes in the memory:

assembly {
    new_ptr := mload(0x40)
    mstore(0x40, add(ptr, and(add(add(sizeOfNewDataBlob, 0x20), 0x1f), not(0x1f))))
}

As you can see it does not assume that the free memory is zeroed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, then I think it makes sense to uncomment this mstore operation, it should not consume a lot of gas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use uintStorage as a storage for bytes32
3 participants