You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.3 KiB
211 lines
6.3 KiB
4 years ago
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||
|
pragma solidity >=0.6.11;
|
||
|
|
||
4 years ago
|
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
|
||
4 years ago
|
import "./Common.sol";
|
||
4 years ago
|
import "./Merkle.sol";
|
||
4 years ago
|
import "./Queue.sol";
|
||
4 years ago
|
import {OpticsHandlerI} from "./UsingOptics.sol";
|
||
4 years ago
|
|
||
4 years ago
|
abstract contract Replica is Common, QueueManager {
|
||
|
using QueueLib for QueueLib.Queue;
|
||
4 years ago
|
|
||
4 years ago
|
uint32 public immutable ownDomain;
|
||
4 years ago
|
uint256 public optimisticSeconds;
|
||
|
|
||
4 years ago
|
mapping(bytes32 => uint256) public confirmAt;
|
||
4 years ago
|
|
||
|
constructor(
|
||
4 years ago
|
uint32 _originDomain,
|
||
|
uint32 _ownDomain,
|
||
4 years ago
|
address _updater,
|
||
|
uint256 _optimisticSeconds,
|
||
4 years ago
|
bytes32 _current
|
||
4 years ago
|
) Common(_originDomain, _updater, _current) QueueManager() {
|
||
|
ownDomain = _ownDomain;
|
||
4 years ago
|
optimisticSeconds = _optimisticSeconds;
|
||
4 years ago
|
current = _current;
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
function fail() internal override {
|
||
|
_setFailed();
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
/// Hook for tasks
|
||
|
function _beforeConfirm() internal virtual;
|
||
|
|
||
|
/// Hook for tasks
|
||
|
function _beforeUpdate() internal virtual;
|
||
|
|
||
4 years ago
|
function nextPending()
|
||
4 years ago
|
external
|
||
|
view
|
||
|
returns (bytes32 _pending, uint256 _confirmAt)
|
||
|
{
|
||
|
if (queue.length() != 0) {
|
||
|
_pending = queue.peek();
|
||
|
_confirmAt = confirmAt[_pending];
|
||
|
}
|
||
|
}
|
||
|
|
||
4 years ago
|
// TODO: refactor to queue
|
||
4 years ago
|
function update(
|
||
|
bytes32 _oldRoot,
|
||
4 years ago
|
bytes32 _newRoot,
|
||
4 years ago
|
bytes memory _signature
|
||
|
) external notFailed {
|
||
4 years ago
|
if (queue.length() > 0) {
|
||
4 years ago
|
require(_oldRoot == queue.lastItem(), "not end of queue");
|
||
4 years ago
|
} else {
|
||
4 years ago
|
require(current == _oldRoot, "not current update");
|
||
4 years ago
|
}
|
||
4 years ago
|
require(Common.checkSig(_oldRoot, _newRoot, _signature), "bad sig");
|
||
4 years ago
|
|
||
4 years ago
|
_beforeUpdate();
|
||
|
|
||
4 years ago
|
confirmAt[_newRoot] = block.timestamp + optimisticSeconds;
|
||
|
queue.enqueue(_newRoot);
|
||
4 years ago
|
|
||
|
emit Update(originSLIP44, _oldRoot, _newRoot, _signature);
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
function canConfirm() external view returns (bool) {
|
||
|
return
|
||
|
queue.length() != 0 && block.timestamp >= confirmAt[queue.peek()];
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
function confirm() external notFailed {
|
||
4 years ago
|
require(queue.length() != 0, "no pending");
|
||
4 years ago
|
|
||
|
bytes32 _pending;
|
||
|
uint256 _now = block.timestamp;
|
||
4 years ago
|
|
||
|
uint256 _remaining = queue.length();
|
||
|
while (_remaining > 0 && _now >= confirmAt[queue.peek()]) {
|
||
4 years ago
|
_pending = queue.dequeue();
|
||
|
delete confirmAt[_pending];
|
||
4 years ago
|
_remaining -= 1;
|
||
4 years ago
|
}
|
||
|
|
||
|
// This condition is hit if the while loop is never executed, because
|
||
|
// the first queue item has not hit its timer yet
|
||
4 years ago
|
require(_pending != bytes32(0), "not time");
|
||
4 years ago
|
|
||
|
_beforeConfirm();
|
||
|
|
||
4 years ago
|
current = _pending;
|
||
4 years ago
|
}
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
contract ProcessingReplica is Replica {
|
||
4 years ago
|
using MerkleLib for MerkleLib.Tree;
|
||
|
using TypedMemView for bytes;
|
||
|
using TypedMemView for bytes29;
|
||
4 years ago
|
using Message for bytes29;
|
||
4 years ago
|
|
||
4 years ago
|
// minimum gas for message processing
|
||
4 years ago
|
uint256 public constant PROCESS_GAS = 500000;
|
||
4 years ago
|
// reserved gas (to ensure tx completes in case message processing runs out)
|
||
4 years ago
|
uint256 public constant RESERVE_GAS = 10000;
|
||
4 years ago
|
|
||
4 years ago
|
bytes32 public previous; // to smooth over witness invalidation
|
||
|
uint256 public lastProcessed;
|
||
4 years ago
|
mapping(bytes32 => MessageStatus) public messages;
|
||
|
enum MessageStatus {None, Pending, Processed}
|
||
|
|
||
|
constructor(
|
||
4 years ago
|
uint32 _originDomain,
|
||
|
uint32 _ownDomain,
|
||
4 years ago
|
address _updater,
|
||
|
uint256 _optimisticSeconds,
|
||
|
bytes32 _start,
|
||
|
uint256 _lastProcessed
|
||
4 years ago
|
) Replica(_originDomain, _ownDomain, _updater, _optimisticSeconds, _start) {
|
||
4 years ago
|
lastProcessed = _lastProcessed;
|
||
|
}
|
||
|
|
||
4 years ago
|
function _beforeConfirm() internal override {
|
||
|
previous = current;
|
||
|
}
|
||
|
|
||
|
function _beforeUpdate() internal override {}
|
||
4 years ago
|
|
||
4 years ago
|
function process(bytes memory _message)
|
||
|
public
|
||
4 years ago
|
returns (bool _success, bytes memory _result)
|
||
4 years ago
|
{
|
||
4 years ago
|
bytes29 _m = _message.ref(0);
|
||
|
|
||
4 years ago
|
uint32 _sequence = _m.sequence();
|
||
4 years ago
|
require(_m.destination() == ownDomain, "!destination");
|
||
4 years ago
|
require(_sequence == lastProcessed + 1, "!sequence");
|
||
4 years ago
|
require(
|
||
|
messages[keccak256(_message)] == MessageStatus.Pending,
|
||
|
"not pending"
|
||
|
);
|
||
4 years ago
|
|
||
|
// Set the state now. We will set lastProcessed later. This prevents
|
||
|
// re-entry as one of the two require statements above will definitely
|
||
|
// fail.
|
||
4 years ago
|
messages[_m.keccak()] = MessageStatus.Processed;
|
||
4 years ago
|
|
||
4 years ago
|
// TODO: assembly this to avoid the clone?
|
||
|
bytes memory payload = _m.body().clone();
|
||
4 years ago
|
address recipient = _m.recipientAddress();
|
||
4 years ago
|
|
||
|
// NB:
|
||
|
// A call running out of gas TYPICALLY errors the whole tx. We want to
|
||
|
// a) ensure the call has a sufficient amount of gas to make a
|
||
|
// meaningful state change.
|
||
|
// b) ensure that if the subcall runs out of gas, that the tx as a whole
|
||
|
// does not revert (i.e. we still mark the message processed)
|
||
|
// To do this, we require that we have enough gas to process
|
||
|
// and still return. We then delegate only the minimum processing gas.
|
||
|
require(gasleft() >= PROCESS_GAS + RESERVE_GAS, "!gas");
|
||
|
// transparently return.
|
||
4 years ago
|
|
||
|
try
|
||
|
OpticsHandlerI(recipient).handle{gas: PROCESS_GAS}(
|
||
|
_m.origin(),
|
||
|
_m.sender(),
|
||
|
payload
|
||
|
)
|
||
|
returns (bytes memory _response) {
|
||
|
_success = true;
|
||
|
_result = _response;
|
||
|
} catch (bytes memory _err) {
|
||
|
_success = false;
|
||
|
_result = _err;
|
||
|
}
|
||
|
|
||
|
// (_success, _ret) = recipient.call{gas: PROCESS_GAS}(payload);
|
||
4 years ago
|
lastProcessed = _sequence;
|
||
4 years ago
|
}
|
||
|
|
||
|
function prove(
|
||
|
bytes32 leaf,
|
||
|
bytes32[32] calldata proof,
|
||
|
uint256 index
|
||
4 years ago
|
) public returns (bool) {
|
||
4 years ago
|
bytes32 actual = MerkleLib.branchRoot(leaf, proof, index);
|
||
4 years ago
|
|
||
4 years ago
|
// NB:
|
||
|
// For convenience, we allow proving against the previous root.
|
||
|
// This means that witnesses don't need to be updated for the new root
|
||
4 years ago
|
if (actual == current || actual == previous) {
|
||
4 years ago
|
messages[leaf] = MessageStatus.Pending;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
4 years ago
|
|
||
|
function proveAndProcess(
|
||
4 years ago
|
bytes memory message,
|
||
4 years ago
|
bytes32[32] calldata proof,
|
||
4 years ago
|
uint256 index
|
||
4 years ago
|
) external {
|
||
4 years ago
|
require(prove(keccak256(message), proof, index), "!prove");
|
||
4 years ago
|
process(message);
|
||
|
}
|
||
4 years ago
|
}
|