From 97d5212d7c068e920f954ec10640d9c57cf19482 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Mon, 12 Jun 2023 11:48:04 -0300 Subject: [PATCH 01/25] chore(arbitration): create `permissionless-arbitration` project --- .gitmodules | 3 +++ .../permissionless-arbitration/foundry.toml | 6 +++++ .../permissionless-arbitration/lib/forge-std | 1 + .../src/Counter.sol | 14 +++++++++++ .../test/Counter.t.sol | 24 +++++++++++++++++++ 5 files changed, 48 insertions(+) create mode 100644 onchain/permissionless-arbitration/foundry.toml create mode 160000 onchain/permissionless-arbitration/lib/forge-std create mode 100644 onchain/permissionless-arbitration/src/Counter.sol create mode 100644 onchain/permissionless-arbitration/test/Counter.t.sol diff --git a/.gitmodules b/.gitmodules index 1dc051b26..0fb03e1fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "onchain/rollups/lib/forge-std"] path = onchain/rollups/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "onchain/permissionless-arbitration/lib/forge-std"] + path = onchain/permissionless-arbitration/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/onchain/permissionless-arbitration/foundry.toml b/onchain/permissionless-arbitration/foundry.toml new file mode 100644 index 000000000..4ff40c48f --- /dev/null +++ b/onchain/permissionless-arbitration/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/onchain/permissionless-arbitration/lib/forge-std b/onchain/permissionless-arbitration/lib/forge-std new file mode 160000 index 000000000..66bf4e2c9 --- /dev/null +++ b/onchain/permissionless-arbitration/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 66bf4e2c92cf507531599845e8d5a08cc2e3b5bb diff --git a/onchain/permissionless-arbitration/src/Counter.sol b/onchain/permissionless-arbitration/src/Counter.sol new file mode 100644 index 000000000..aded7997b --- /dev/null +++ b/onchain/permissionless-arbitration/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/onchain/permissionless-arbitration/test/Counter.t.sol b/onchain/permissionless-arbitration/test/Counter.t.sol new file mode 100644 index 000000000..30235e8a8 --- /dev/null +++ b/onchain/permissionless-arbitration/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From bfee4261b175f36f08968f6a74113ad05d4189d0 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Wed, 14 Jun 2023 12:00:24 -0300 Subject: [PATCH 02/25] feat(arbitration): add smart contracts --- .gitmodules | 3 + onchain/permissionless-arbitration/.gitignore | 21 + .../permissionless-arbitration/coverage.sh | 3 + .../permissionless-arbitration/foundry.toml | 4 +- .../lib/machine-solidity-step | 1 + .../src/CanonicalConstants.sol | 50 +++ .../permissionless-arbitration/src/Clock.sol | 157 +++++++ .../src/Commitment.sol | 34 ++ .../src/Counter.sol | 14 - .../src/Machine.sol | 22 + .../permissionless-arbitration/src/Match.sol | 273 ++++++++++++ .../permissionless-arbitration/src/Merkle.sol | 50 +++ .../permissionless-arbitration/src/Time.sol | 99 +++++ .../permissionless-arbitration/src/Tree.sol | 50 +++ .../tournament/abstracts/LeafTournament.sol | 208 ++++++++++ .../abstracts/NonLeafTournament.sol | 163 ++++++++ .../abstracts/NonRootTournament.sol | 81 ++++ .../tournament/abstracts/RootTournament.sol | 45 ++ .../src/tournament/abstracts/Tournament.sol | 386 +++++++++++++++++ .../tournament/concretes/BottomTournament.sol | 37 ++ .../tournament/concretes/MiddleTournament.sol | 33 ++ .../concretes/SingleLevelTournament.sol | 18 + .../tournament/concretes/TopTournament.sol | 16 + .../factories/InnerTournamentFactory.sol | 110 +++++ .../factories/RootTournamentFactory.sol | 51 +++ .../interfaces/IInnerTournamentFactory.sol | 21 + .../interfaces/IMultiTournamentFactory.sol | 24 ++ .../interfaces/IRootTournamentFactory.sol | 17 + .../interfaces/ISingleTournamentFactory.sol | 13 + .../test/Counter.t.sol | 24 -- .../test/Match.t.sol | 162 ++++++++ .../test/MultiTournament.t.sol | 392 ++++++++++++++++++ .../test/Tournament.t.sol | 218 ++++++++++ .../test/TournamentFactory.t.sol | 77 ++++ .../permissionless-arbitration/test/Util.sol | 218 ++++++++++ 35 files changed, 3056 insertions(+), 39 deletions(-) create mode 100644 onchain/permissionless-arbitration/.gitignore create mode 100755 onchain/permissionless-arbitration/coverage.sh create mode 160000 onchain/permissionless-arbitration/lib/machine-solidity-step create mode 100644 onchain/permissionless-arbitration/src/CanonicalConstants.sol create mode 100644 onchain/permissionless-arbitration/src/Clock.sol create mode 100644 onchain/permissionless-arbitration/src/Commitment.sol delete mode 100644 onchain/permissionless-arbitration/src/Counter.sol create mode 100644 onchain/permissionless-arbitration/src/Machine.sol create mode 100644 onchain/permissionless-arbitration/src/Match.sol create mode 100644 onchain/permissionless-arbitration/src/Merkle.sol create mode 100644 onchain/permissionless-arbitration/src/Time.sol create mode 100644 onchain/permissionless-arbitration/src/Tree.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/test/Counter.t.sol create mode 100644 onchain/permissionless-arbitration/test/Match.t.sol create mode 100644 onchain/permissionless-arbitration/test/MultiTournament.t.sol create mode 100644 onchain/permissionless-arbitration/test/Tournament.t.sol create mode 100644 onchain/permissionless-arbitration/test/TournamentFactory.t.sol create mode 100644 onchain/permissionless-arbitration/test/Util.sol diff --git a/.gitmodules b/.gitmodules index 0fb03e1fd..6d908e898 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "onchain/permissionless-arbitration/lib/forge-std"] path = onchain/permissionless-arbitration/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "onchain/permissionless-arbitration/lib/machine-solidity-step"] + path = onchain/permissionless-arbitration/lib/machine-solidity-step + url = https://github.com/cartesi/machine-solidity-step diff --git a/onchain/permissionless-arbitration/.gitignore b/onchain/permissionless-arbitration/.gitignore new file mode 100644 index 000000000..3f738c3f0 --- /dev/null +++ b/onchain/permissionless-arbitration/.gitignore @@ -0,0 +1,21 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env + +# Coverage +report +lcov.info + +# Test +test/uarch-log diff --git a/onchain/permissionless-arbitration/coverage.sh b/onchain/permissionless-arbitration/coverage.sh new file mode 100755 index 000000000..aac315d20 --- /dev/null +++ b/onchain/permissionless-arbitration/coverage.sh @@ -0,0 +1,3 @@ +#!/bin/bash +forge coverage --report lcov +genhtml -o report --branch-coverage lcov.info diff --git a/onchain/permissionless-arbitration/foundry.toml b/onchain/permissionless-arbitration/foundry.toml index 4ff40c48f..1496f241d 100644 --- a/onchain/permissionless-arbitration/foundry.toml +++ b/onchain/permissionless-arbitration/foundry.toml @@ -3,4 +3,6 @@ src = "src" out = "out" libs = ["lib"] -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file +remappings = [ + 'step/=lib/machine-solidity-step/', +] diff --git a/onchain/permissionless-arbitration/lib/machine-solidity-step b/onchain/permissionless-arbitration/lib/machine-solidity-step new file mode 160000 index 000000000..f615814c6 --- /dev/null +++ b/onchain/permissionless-arbitration/lib/machine-solidity-step @@ -0,0 +1 @@ +Subproject commit f615814c6d758b505d55d7336d403e98c0e79135 diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol new file mode 100644 index 000000000..300dce0bd --- /dev/null +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Time.sol"; + +library ArbitrationConstants { + // maximum tolerance time for participant being censored + // Time.Duration constant CENSORSHIP_TOLERANCE = + // Time.Duration.wrap(60 * 60 * 24 * 7); + + // maximum time for replaying the computation offchain + // Time.Duration constant VALIDATOR_EFFORT = + // Time.Duration.wrap(60 * 60 * 24 * 7); // TODO + + // Dummy + Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(45); + Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(45); + + Time.Duration constant DISPUTE_TIMEOUT = + Time.Duration.wrap( + Time.Duration.unwrap(CENSORSHIP_TOLERANCE) + + Time.Duration.unwrap(VALIDATOR_EFFORT) + ); + + // 4-level tournament + uint64 constant LEVELS = 4; + uint64 constant LOG2_MAX_MCYCLE = 63; + + /// @return log2step gap of each leaf in the tournament[level] + function log2step(uint64 level) internal pure returns (uint64) { + uint64[LEVELS] memory arr = [ + uint64(24), + uint64(14), + uint64(7), + uint64(0) + ]; + return arr[level]; + } + + /// @return height of the tournament[level] tree which is calculated by subtracting the log2step[level] from the log2step[level - 1] + function height(uint64 level) internal pure returns (uint64) { + uint64[LEVELS] memory arr = [ + uint64(39), + uint64(10), + uint64(7), + uint64(7) + ]; + return arr[level]; + } +} diff --git a/onchain/permissionless-arbitration/src/Clock.sol b/onchain/permissionless-arbitration/src/Clock.sol new file mode 100644 index 000000000..67a9ecd2c --- /dev/null +++ b/onchain/permissionless-arbitration/src/Clock.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Time.sol"; +import "./CanonicalConstants.sol"; + +library Clock { + using Time for Time.Instant; + using Time for Time.Duration; + + using Clock for State; + + struct State { + Time.Duration allowance; + Time.Instant startInstant; // the timestamp when the clock started ticking, zero means clock is paused + } + + // + // View/Pure methods + // + + function notInitialized(State memory state) internal pure returns (bool) { + return state.allowance.isZero(); + } + + function requireInitialized(State memory state) internal pure { + require(!state.notInitialized(), "clock is not initialized"); + } + + function requireNotInitialized(State memory state) internal pure { + require(state.notInitialized(), "clock is initialized"); + } + + function hasTimeLeft(State memory state) internal view returns (bool) { + if (state.startInstant.isZero()) { + return true; + } else { + return + state.allowance.gt( + Time.timeSpan(Time.currentTime(), state.startInstant) + ); + } + } + + /// @return deadline of the two clocks should be the tolerances combined + function deadline( + State memory freshState1, + State memory freshState2 + ) internal view returns (Time.Instant) { + Time.Duration duration = freshState1.allowance.add( + freshState2.allowance + ); + return Time.currentTime().add(duration); + } + + /// @return max tolerance of the two clocks + function max( + State memory pausedState1, + State memory pausedState2 + ) internal pure returns (Time.Duration) { + if (pausedState1.allowance.gt(pausedState2.allowance)) { + return pausedState1.allowance; + } else { + return pausedState2.allowance; + } + } + + /// @return duration of time has elapsed since the clock timeout + function timeSinceTimeout( + State memory state + ) internal view returns (Time.Duration) { + return + Time.timeSpan(Time.currentTime(), state.startInstant).monus( + state.allowance + ); + } + + // + // Storage methods + // + + function setNewPaused( + State storage state, + Time.Instant checkinInstant, + Time.Duration initialAllowance + ) internal { + Time.Duration allowance = initialAllowance.monus( + Time.currentTime().timeSpan(checkinInstant) + ); + + if (allowance.isZero()) { + revert("can't create clock with zero time"); + } + + state.allowance = allowance; + state.startInstant = Time.ZERO_INSTANT; + } + + /// @notice Resume the clock from pause state, or pause a clock and update the allowance + function advanceClock(State storage state) internal { + Time.Duration _timeLeft = timeLeft(state); + + if (_timeLeft.isZero()) { + revert("can't advance clock with no time left"); + } + + toggleClock(state); + state.allowance = _timeLeft; + } + + function addValidatorEffort(State storage state, Time.Duration deduction) internal { + Time.Duration _timeLeft = state.allowance.monus( + deduction + ); + + if (_timeLeft.isZero()) { + revert("can't reset clock with no time left"); + } + + Time.Duration _allowance = _timeLeft.add(ArbitrationConstants.VALIDATOR_EFFORT); + if (_allowance.gt(ArbitrationConstants.DISPUTE_TIMEOUT)) { + _allowance = ArbitrationConstants.DISPUTE_TIMEOUT; + } + + state.allowance = _allowance; + state.startInstant = Time.ZERO_INSTANT; + } + + function setPaused(State storage state) internal { + if (!state.startInstant.isZero()) { + state.advanceClock(); + } + } + + // + // Private + // + + function timeLeft(State memory state) private view returns (Time.Duration) { + if (state.startInstant.isZero()) { + return state.allowance; + } else { + return + state.allowance.monus( + Time.timeSpan(Time.currentTime(), state.startInstant) + ); + } + } + + function toggleClock(State storage state) private { + if (state.startInstant.isZero()) { + state.startInstant = Time.currentTime(); + } else { + state.startInstant = Time.ZERO_INSTANT; + } + } +} diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol new file mode 100644 index 000000000..01df9569f --- /dev/null +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./CanonicalConstants.sol"; +import "./Tree.sol"; +import "./Machine.sol"; + +// import "./Merkle.sol"; + +library Commitment { + using Commitment for Tree.Node; + + function proveFinalState( + Tree.Node root, + uint64 level, + Machine.Hash finalState, + bytes32[] calldata hashProof + ) internal pure { + root.proveHash( + uint64(1 << ArbitrationConstants.height(level)), + finalState, + hashProof + ); + } + + function proveHash( + Tree.Node root, + uint64 position, + Machine.Hash hash, + bytes32[] calldata hashProof + ) internal pure { + // TODO: call Merkle library + } +} diff --git a/onchain/permissionless-arbitration/src/Counter.sol b/onchain/permissionless-arbitration/src/Counter.sol deleted file mode 100644 index aded7997b..000000000 --- a/onchain/permissionless-arbitration/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/onchain/permissionless-arbitration/src/Machine.sol b/onchain/permissionless-arbitration/src/Machine.sol new file mode 100644 index 000000000..fde20e0f4 --- /dev/null +++ b/onchain/permissionless-arbitration/src/Machine.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +library Machine { + type Hash is bytes32; + + Hash constant ZERO_STATE = Hash.wrap(0x0); + + function notInitialized(Hash hash) internal pure returns (bool) { + bytes32 h = Hash.unwrap(hash); + return h == 0x0; + } + + function eq(Hash left, Hash right) internal pure returns (bool) { + bytes32 l = Hash.unwrap(left); + bytes32 r = Hash.unwrap(right); + return l == r; + } + + type Cycle is uint256; // TODO overcomplicated? + type Log2Step is uint64; // TODO overcomplicated? +} diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol new file mode 100644 index 000000000..903d346e8 --- /dev/null +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./CanonicalConstants.sol"; +import "./Tree.sol"; +import "./Machine.sol"; + +/// @notice Implements functionalities to advance a match, until the point where divergence is found. +library Match { + using Tree for Tree.Node; + using Match for Id; + using Match for IdHash; + using Match for State; + + // + // Id + // + + struct Id { + Tree.Node commitmentOne; + Tree.Node commitmentTwo; + } + + // + // IdHash + // + type IdHash is bytes32; + + IdHash constant ZERO_ID = IdHash.wrap(bytes32(0x0)); + + function hashFromId(Id memory id) internal pure returns (IdHash) { + return IdHash.wrap(keccak256(abi.encode(id))); + } + + function isZero(IdHash idHash) internal pure returns (bool) { + return IdHash.unwrap(idHash) == 0x0; + } + + function eq(IdHash left, IdHash right) internal pure returns (bool) { + bytes32 l = IdHash.unwrap(left); + bytes32 r = IdHash.unwrap(right); + return l == r; + } + + function requireEq(IdHash left, IdHash right) internal pure { + require(left.eq(right), "matches are not equal"); + } + + function requireExist(IdHash idHash) internal pure { + require(!idHash.isZero(), "match doesn't exist"); + } + + // + // State + // + + struct State { + Tree.Node otherParent; + Tree.Node leftNode; + Tree.Node rightNode; + // Once match is done, leftNode and rightNode change meaning + // and contains contested final states. + uint64 runningLeafPosition; + uint64 height; + uint64 currentHeight; + } + + function createMatch( + Tree.Node one, + Tree.Node two, + Tree.Node leftNodeOfTwo, + Tree.Node rightNodeOfTwo, + uint64 height + ) internal pure returns (IdHash, State memory) { + assert(two.verify(leftNodeOfTwo, rightNodeOfTwo)); + + Id memory matchId = Id(one, two); + + State memory state = State( + one, + leftNodeOfTwo, + rightNodeOfTwo, + 0, + height, // TODO + height // TODO + ); + + return (matchId.hashFromId(), state); + } + + function goDownLeftTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.leftNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.currentHeight--; + } + + function goDownRightTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.rightNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.runningLeafPosition += uint64(1 << state.currentHeight); // TODO: verify + state.currentHeight--; + } + + function setDivergenceOnLeftLeaf( + State storage state, + Tree.Node leftLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.rightNode = leftLeaf; + state.currentHeight = 0; + + if (state.height % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } + + function setDivergenceOnRightLeaf( + State storage state, + Tree.Node rightLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.leftNode = rightLeaf; + state.runningLeafPosition += 1; // TODO: verify + state.currentHeight = 0; + + if (state.height % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + + function getDivergence( + State storage state + ) + internal + view + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 0); + + if (state.runningLeafPosition % 2 == 0) { + // divergence was set on left leaf + if (state.height % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } else { + // divergence was set on right leaf + if (state.height % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + } + + function setInitialState( + State storage state, + Machine.Hash initialState + ) internal { + state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); + } + + // + // View methods + // + + function exists(State memory state) internal pure returns (bool) { + return !state.otherParent.isZero(); + } + + function isFinished(State memory state) internal pure returns (bool) { + return state.currentHeight == 0; + } + + function canBeFinalized(State memory state) internal pure returns (bool) { + return state.currentHeight == 1; + } + + function canBeAdvanced(State memory state) internal pure returns (bool) { + return state.currentHeight > 1; + } + + function agreesOnLeftNode( + State memory state, + Tree.Node newLeftNode + ) internal pure returns (bool) { + return newLeftNode.eq(state.leftNode); + } + + function toCycle( + State memory state, + uint256 startCycle, + uint64 level + ) internal pure returns (uint256) { + uint64 log2step = ArbitrationConstants.log2step(level); + return _toCycle(state, startCycle, log2step); + } + + // + // Requires + // + + function requireExist(State memory state) internal pure { + require(state.exists(), "match does not exist"); + } + + function requireIsFinished(State memory state) internal pure { + require(state.isFinished(), "match is not finished"); + } + + function requireCanBeFinalized(State memory state) internal pure { + require(state.canBeFinalized(), "match is not ready to be finalized"); + } + + function requireCanBeAdvanced(State memory state) internal pure { + require(!state.canBeFinalized(), "match is ready to be finalized"); + } + + function requireParentHasChildren( + State memory state, + Tree.Node leftNode, + Tree.Node rightNode + ) internal pure { + state.otherParent.requireChildren(leftNode, rightNode); + } + + // + // Private + // + + function _toCycle( + State memory state, + uint256 base, + uint64 log2step + ) internal pure returns (uint256) { + uint256 step = 1 << log2step; + uint256 leafPosition = state.runningLeafPosition + 1; // +1 is implicit initialHash + return base + (leafPosition * step); // TODO verify + } +} diff --git a/onchain/permissionless-arbitration/src/Merkle.sol b/onchain/permissionless-arbitration/src/Merkle.sol new file mode 100644 index 000000000..e5e309fbc --- /dev/null +++ b/onchain/permissionless-arbitration/src/Merkle.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +library Merkle { + function getRootWithValue( + uint64 _position, + bytes8 _value, + bytes32[] memory _proof + ) public pure returns (bytes32) { + bytes32 _runningHash = keccak256(abi.encodePacked(_value)); + + return getRootWithDrive(_position, 3, _runningHash, _proof); + } + + function getRootWithHash( + uint64 _position, + bytes32 _hash, + bytes32[] memory _proof + ) public pure returns (bytes32) { + return getRootWithDrive(_position, 3, _hash, _proof); + } + + function getRootWithDrive( + uint64 _position, + uint8 _logOfSize, + bytes32 _drive, + bytes32[] memory _siblings + ) public pure returns (bytes32) { + require(_logOfSize >= 3, "Must be at least a word"); + require(_logOfSize <= 64, "Cannot be bigger than the machine itself"); + + uint64 _size = uint64(2) ** _logOfSize; + + require(((_size - 1) & _position) == 0, "Position is not aligned"); + require( + _siblings.length == 64 - _logOfSize, + "Proof length does not match" + ); + + for (uint64 _i = 0; _i < _siblings.length; _i++) { + if ((_position & (_size << _i)) == 0) { + _drive = keccak256(abi.encodePacked(_drive, _siblings[_i])); + } else { + _drive = keccak256(abi.encodePacked(_siblings[_i], _drive)); + } + } + + return _drive; + } +} diff --git a/onchain/permissionless-arbitration/src/Time.sol b/onchain/permissionless-arbitration/src/Time.sol new file mode 100644 index 000000000..5ef924d20 --- /dev/null +++ b/onchain/permissionless-arbitration/src/Time.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +library Time { + type Instant is uint64; + type Duration is uint64; + + using Time for Instant; // TODO rename to Instant + using Time for Duration; + + Instant constant ZERO_INSTANT = Instant.wrap(0); + Duration constant ZERO_DURATION = Duration.wrap(0); + + function currentTime() internal view returns (Instant) { + return Instant.wrap(uint64(block.timestamp)); + } + + function add( + Instant timestamp, + Duration duration + ) internal pure returns (Instant) { + uint64 t = Instant.unwrap(timestamp); + uint64 d = Duration.unwrap(duration); + return Instant.wrap(t + d); + } + + function gt(Instant left, Instant right) internal pure returns (bool) { + uint64 l = Instant.unwrap(left); + uint64 r = Instant.unwrap(right); + return l > r; + } + + function gt(Duration left, Duration right) internal pure returns (bool) { + uint64 l = Duration.unwrap(left); + uint64 r = Duration.unwrap(right); + return l > r; + } + + function isZero(Instant timestamp) internal pure returns (bool) { + uint64 t = Instant.unwrap(timestamp); + return t == 0; + } + + function isZero(Duration duration) internal pure returns (bool) { + uint64 d = Duration.unwrap(duration); + return d == 0; + } + + function add( + Duration left, + Duration right + ) internal pure returns (Duration) { + uint64 l = Duration.unwrap(left); + uint64 r = Duration.unwrap(right); + return Duration.wrap(l + r); + } + + function sub( + Duration left, + Duration right + ) internal pure returns (Duration) { + uint64 l = Duration.unwrap(left); + uint64 r = Duration.unwrap(right); + return Duration.wrap(l - r); + } + + function monus( + Duration left, + Duration right + ) internal pure returns (Duration) { + uint64 l = Duration.unwrap(left); + uint64 r = Duration.unwrap(right); + return Duration.wrap(l < r ? 0 : l - r); + } + + function timeSpan( + Instant left, + Instant right + ) internal pure returns (Duration) { + uint64 l = Instant.unwrap(left); + uint64 r = Instant.unwrap(right); + return Duration.wrap(l - r); + } + + function timeoutElapsedSince( + Instant timestamp, + Duration duration, + Instant current + ) internal pure returns (bool) { + return !timestamp.add(duration).gt(current); + } + + function timeoutElapsed( + Instant timestamp, + Duration duration + ) internal view returns (bool) { + return timestamp.timeoutElapsedSince(duration, currentTime()); + } +} diff --git a/onchain/permissionless-arbitration/src/Tree.sol b/onchain/permissionless-arbitration/src/Tree.sol new file mode 100644 index 000000000..e56c49fda --- /dev/null +++ b/onchain/permissionless-arbitration/src/Tree.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Machine.sol"; + +library Tree { + using Tree for Node; + + type Node is bytes32; + + Node constant ZERO_NODE = Node.wrap(bytes32(0x0)); + + function eq(Node left, Node right) internal pure returns (bool) { + bytes32 l = Node.unwrap(left); + bytes32 r = Node.unwrap(right); + return l == r; + } + + function join(Node left, Node right) internal pure returns (Node) { + bytes32 l = Node.unwrap(left); + bytes32 r = Node.unwrap(right); + bytes32 p = keccak256(abi.encode(l, r)); + return Node.wrap(p); + } + + function verify( + Node parent, + Node left, + Node right + ) internal pure returns (bool) { + return parent.eq(left.join(right)); + } + + function requireChildren(Node parent, Node left, Node right) internal pure { + require(parent.verify(left, right), "child nodes don't match parent"); + } + + function isZero(Node node) internal pure returns (bool) { + bytes32 n = Node.unwrap(node); + return n == 0x0; + } + + function requireExist(Node node) internal pure { + require(!node.isZero(), "tree node doesn't exist"); + } + + function toMachineHash(Node node) internal pure returns (Machine.Hash) { + return Machine.Hash.wrap(Node.unwrap(node)); + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol new file mode 100644 index 000000000..ce41b2ad3 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Tournament.sol"; +import "../../Commitment.sol"; +import "../../Merkle.sol"; +import "step/contracts/interfaces/IUArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; +import "step/contracts/interfaces/IMemoryAccessLog.sol"; + +/// @notice Leaf tournament is the one that seals leaf match +abstract contract LeafTournament is Tournament { + using Machine for Machine.Hash; + using Commitment for Tree.Node; + using Tree for Tree.Node; + using Clock for Clock.State; + using Match for Match.Id; + using Match for Match.State; + + IUArchState immutable stateInterface; + IUArchStep immutable stepInterface; + + constructor(IUArchState _stateInterface, IUArchStep _stepInterface) { + stateInterface = _stateInterface; + stepInterface = _stepInterface; + } + + function sealLeafMatch( + Match.Id calldata _matchId, + Tree.Node _leftLeaf, + Tree.Node _rightLeaf, + Machine.Hash _initialHash, + bytes32[] calldata _initialHashProof + ) external tournamentNotFinished { + Match.State storage _matchState = matches[_matchId.hashFromId()]; + _matchState.requireExist(); + _matchState.requireCanBeFinalized(); + _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); + + Machine.Hash _finalStateOne; + Machine.Hash _finalStateTwo; + + if (!_matchState.agreesOnLeftNode(_leftLeaf)) { + // Divergence is in the left leaf! + (_finalStateOne, _finalStateTwo) = _matchState + .setDivergenceOnLeftLeaf(_leftLeaf); + } else { + // Divergence is in the right leaf! + (_finalStateOne, _finalStateTwo) = _matchState + .setDivergenceOnRightLeaf(_rightLeaf); + } + + // Unpause clocks + Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; + Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; + _clock1.setPaused(); + _clock1.advanceClock(); + _clock2.setPaused(); + _clock2.advanceClock(); + + // Prove initial hash is in commitment + if (_matchState.runningLeafPosition == 0) { + require(_initialHash.eq(initialHash), "initial hash incorrect"); + } else { + _matchId.commitmentOne.proveHash( + _matchState.runningLeafPosition - 1, + _initialHash, + _initialHashProof + ); + } + + _matchState.setInitialState(_initialHash); + } + + // TODO: do validate access logs, do solidity-step + function winLeafMatch( + Match.Id calldata _matchId, + IMemoryAccessLog.AccessLogs calldata _accessLogs, + bytes32[] calldata _oldHashes, + bytes32[][] calldata _proofs, + Tree.Node _leftNode, + Tree.Node _rightNode + ) external tournamentNotFinished { + Match.State storage _matchState = matches[_matchId.hashFromId()]; + _matchState.requireExist(); + _matchState.requireIsFinished(); + + Clock.State storage _clockOne = clocks[_matchId.commitmentOne]; + Clock.State storage _clockTwo = clocks[_matchId.commitmentTwo]; + _clockOne.requireInitialized(); + _clockTwo.requireInitialized(); + + require( + _accessLogs.logs.length == _proofs.length, + "proofs length doesn't match" + ); + + { + // workaround stack too deep problem + Machine.Hash _finalState = executeStep( + _matchState.otherParent.toMachineHash(), + _accessLogs, + _oldHashes, + _proofs + ); + + ( + Machine.Hash _finalStateOne, + Machine.Hash _finalStateTwo + ) = _matchState.getDivergence(); + + if (_leftNode.join(_rightNode).eq(_matchId.commitmentOne)) { + require( + _finalState.eq(_finalStateOne), + "final state one doesn't match" + ); + + _clockOne.addValidatorEffort(Time.ZERO_DURATION); + pairCommitment( + _matchId.commitmentOne, + _clockOne, + _leftNode, + _rightNode + ); + } else if (_leftNode.join(_rightNode).eq(_matchId.commitmentTwo)) { + require( + _finalState.eq(_finalStateTwo), + "final state two doesn't match" + ); + + _clockTwo.addValidatorEffort(Time.ZERO_DURATION); + pairCommitment( + _matchId.commitmentTwo, + _clockTwo, + _leftNode, + _rightNode + ); + } else { + revert("wrong left/right nodes for step"); + } + } + + delete matches[_matchId.hashFromId()]; + } + + function executeStep( + Machine.Hash _initialState, + IMemoryAccessLog.AccessLogs memory _accessLogs, + bytes32[] memory _oldHashes, + bytes32[][] memory _proofs + ) internal returns (Machine.Hash) { + uint256 _writeCount = 0; + Machine.Hash _finalState = _initialState; + for (uint256 _i = 0; _i < _accessLogs.logs.length; _i++) { + if ( + _accessLogs.logs[_i].accessType == + IMemoryAccessLog.AccessType.Write + ) { + // validate write proofs with old value, and update the machine hash with new value + // developer should make sure the _oldHashes array matches the write operations in the _accesses array + require( + _finalState.eq( + Machine.Hash.wrap( + Merkle.getRootWithHash( + _accessLogs.logs[_i].position, + _oldHashes[_writeCount], + _proofs[_i] + ) + ) + ), + "machine hash doesn't match" + ); + + _finalState = Machine.Hash.wrap( + Merkle.getRootWithValue( + _accessLogs.logs[_i].position, + _accessLogs.logs[_i].val, + _proofs[_i] + ) + ); + + _writeCount++; + } else { + // validate read proofs + require( + _finalState.eq( + Machine.Hash.wrap( + Merkle.getRootWithValue( + _accessLogs.logs[_i].position, + _accessLogs.logs[_i].val, + _proofs[_i] + ) + ) + ), + "machine hash doesn't match" + ); + } + } + // call machine-solidity-step to replay accessLogs + IUArchState.State memory _state = IUArchState.State( + stateInterface, + _accessLogs + ); + stepInterface.step(_state); + + return _finalState; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol new file mode 100644 index 000000000..f140d0353 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../interfaces/IInnerTournamentFactory.sol"; +import "./Tournament.sol"; +import "./NonRootTournament.sol"; + +/// @notice Non-leaf tournament can create inner tournaments and matches +abstract contract NonLeafTournament is Tournament { + using Clock for Clock.State; + using Commitment for Tree.Node; + using Machine for Machine.Hash; + using Tree for Tree.Node; + using Time for Time.Instant; + using Match for Match.State; + using Match for Match.Id; + using Match for Match.IdHash; + + // + // Constants + // + + IInnerTournamentFactory immutable innerFactory; + + // + // Storage + // + mapping(NonRootTournament => Match.IdHash) matchIdFromInnerTournaments; + + // + // Events + // + + event newInnerTournament(Match.IdHash indexed, NonRootTournament); + + // + // Modifiers + // + + modifier onlyInnerTournament() { + Match.IdHash matchIdHash = matchIdFromInnerTournaments[ + NonRootTournament(msg.sender) + ]; + matches[matchIdHash].requireExist(); + _; + } + + // + // Constructor + // + + constructor(IInnerTournamentFactory _innerFactory) { + innerFactory = _innerFactory; + } + + function sealInnerMatchAndCreateInnerTournament( + Match.Id calldata _matchId, + Tree.Node _leftLeaf, + Tree.Node _rightLeaf, + Machine.Hash _initialHash, + bytes32[] calldata _initialHashProof + ) external tournamentNotFinished { + Match.State storage _matchState = matches[_matchId.hashFromId()]; + _matchState.requireCanBeFinalized(); + _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); + + Machine.Hash _finalStateOne; + Machine.Hash _finalStateTwo; + + if (!_matchState.agreesOnLeftNode(_leftLeaf)) { + // Divergence is in the left leaf! + (_finalStateOne, _finalStateTwo) = _matchState + .setDivergenceOnLeftLeaf(_leftLeaf); + } else { + // Divergence is in the right leaf! + (_finalStateOne, _finalStateTwo) = _matchState + .setDivergenceOnRightLeaf(_rightLeaf); + } + + // Pause clocks + Time.Duration _maxDuration; + { + Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; + Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; + _clock1.setPaused(); + _clock2.setPaused(); + _maxDuration = Clock.max(_clock1, _clock2); + } + + // Prove initial hash is in commitment + if (_matchState.runningLeafPosition == 0) { + require(_initialHash.eq(initialHash), "initial hash incorrect"); + } else { + _matchId.commitmentOne.proveHash( + _matchState.runningLeafPosition - 1, + _initialHash, + _initialHashProof + ); + } + + NonRootTournament _inner = innerFactory.instantiateInner( + _initialHash, + _matchId.commitmentOne, + _finalStateOne, + _matchId.commitmentTwo, + _finalStateTwo, + _maxDuration, + _matchState.toCycle(startCycle, level), + level + 1 + ); + matchIdFromInnerTournaments[_inner] = _matchId.hashFromId(); + + emit newInnerTournament(_matchId.hashFromId(), _inner); + } + + function winInnerMatch( + NonRootTournament _childTournament, + Tree.Node _leftNode, + Tree.Node _rightNode + ) external tournamentNotFinished { + Match.IdHash _matchIdHash = matchIdFromInnerTournaments[_childTournament]; + _matchIdHash.requireExist(); + + Match.State storage _matchState = matches[_matchIdHash]; + _matchState.requireExist(); + _matchState.requireIsFinished(); + + Tree.Node _winner = _childTournament.tournamentWinner(); + _winner.requireExist(); + + Tree.Node _commitmentRoot = _leftNode.join(_rightNode); + require(_commitmentRoot.eq(_winner), "tournament winner is different"); + + Clock.State storage _clock = clocks[_commitmentRoot]; + _clock.requireInitialized(); + _clock.addValidatorEffort( + Time + .currentTime() + .timeSpan(_childTournament.maximumEnforceableDelay()) + ); + + pairCommitment( + _commitmentRoot, + _clock, + _leftNode, + _rightNode + ); + + // delete storage + delete matches[_matchIdHash]; + matchIdFromInnerTournaments[_childTournament] = Match.ZERO_ID; + } + + + function updateTournamentDelay( + Time.Instant _delay + ) external onlyInnerTournament { + bool overrode = setMaximumDelay(_delay); + if (overrode) { + updateParentTournamentDelay(_delay); + } + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol new file mode 100644 index 000000000..c8ef240ce --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Tournament.sol"; +import "./NonLeafTournament.sol"; + +/// @notice Non-root tournament needs to propagate side-effects to its parent +abstract contract NonRootTournament is Tournament { + using Machine for Machine.Hash; + using Tree for Tree.Node; + + // + // Constants + // + NonLeafTournament immutable parentTournament; + + Tree.Node immutable contestedCommitmentOne; + Machine.Hash immutable contestedFinalStateOne; + Tree.Node immutable contestedCommitmentTwo; + Machine.Hash immutable contestedFinalStateTwo; + + // + // Constructor + // + + constructor( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level, + NonLeafTournament _parent + ) Tournament(_initialHash, _allowance, _startCycle, _level) { + parentTournament = _parent; + + contestedCommitmentOne = _contestedCommitmentOne; + contestedFinalStateOne = _contestedFinalStateOne; + contestedCommitmentTwo = _contestedCommitmentTwo; + contestedFinalStateTwo = _contestedFinalStateTwo; + } + + /// @notice get the dangling commitment at current level and then retrieve the winner commitment + function tournamentWinner() external view override returns (Tree.Node) { + Tree.Node _danglingCommitment = _tournamentWinner(); + + if (_danglingCommitment.isZero()) { + return Tree.ZERO_NODE; + } + + Machine.Hash _finalState = finalStates[_danglingCommitment]; + + if (_finalState.eq(contestedFinalStateOne)) { + return contestedCommitmentOne; + } else { + assert(_finalState.eq(contestedFinalStateTwo)); + return contestedCommitmentTwo; + } + } + + function updateParentTournamentDelay( + Time.Instant _delay + ) internal override { + parentTournament.updateTournamentDelay(_delay); + } + + /// @notice a final state is valid if it's equal to ContestedFinalStateOne or ContestedFinalStateTwo + function validContestedFinalState( + Machine.Hash _finalState + ) internal view override returns (bool) { + if (contestedFinalStateOne.eq(_finalState)) { + return true; + } else if (contestedFinalStateTwo.eq(_finalState)) { + return true; + } else { + return false; + } + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol new file mode 100644 index 000000000..ed1c78269 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./Tournament.sol"; + +/// @notice Root tournament has no parent +abstract contract RootTournament is Tournament { + // + // Constructor + // + + constructor( + Machine.Hash _initialHash + ) Tournament(_initialHash, ArbitrationConstants.CENSORSHIP_TOLERANCE, 0, 0) {} + + function tournamentWinner() external view override returns (Tree.Node) { + return _tournamentWinner(); + } + + function updateParentTournamentDelay( + Time.Instant _delay + ) internal override { + // do nothing, the root tournament has no parent to update + } + + function validContestedFinalState( + Machine.Hash + ) internal pure override returns (bool) { + // always returns true in root tournament + return true; + } + + function rootTournamentFinalState() external view returns (bool, Machine.Hash) { + if (!isFinished()) { + return (false, Machine.ZERO_STATE); + } + + (bool _hasDanglingCommitment, Tree.Node _danglingCommitment) = + hasDanglingCommitment(); + assert(_hasDanglingCommitment); + + Machine.Hash _finalState = finalStates[_danglingCommitment]; + return (true, _finalState); + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol new file mode 100644 index 000000000..50b22045d --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../../CanonicalConstants.sol"; + +import "../../Commitment.sol"; +import "../../Time.sol"; +import "../../Machine.sol"; +import "../../Tree.sol"; +import "../../Clock.sol"; +import "../../Match.sol"; + +/// @notice Implements the core functionalities of a permissionless tournament that resolves +/// disputes of n parties in O(log(n)) +/// @dev tournaments and matches are nested alternately. Anyone can join a tournament +/// while the tournament is still open, and two of the participants with unique commitments +/// will form a match. A match located in the last level is called `leafMatch`, +/// meaning the one-step disagreement is found and can be resolved by solidity-step. +/// Non-leaf (inner) matches would normally create inner tournaments with height = height + 1, +/// to find the divergence with improved precision. +abstract contract Tournament { + using Machine for Machine.Hash; + using Tree for Tree.Node; + using Commitment for Tree.Node; + + using Time for Time.Instant; + using Time for Time.Duration; + + using Clock for Clock.State; + + using Match for Match.Id; + using Match for Match.IdHash; + using Match for Match.State; + + // + // Constants + // + + Machine.Hash immutable initialHash; + + uint256 immutable startCycle; + uint64 immutable level; + + Time.Instant immutable startInstant; + Time.Duration immutable allowance; + + // + // Storage + // + + Time.Instant public maximumEnforceableDelay; + Tree.Node danglingCommitment; + + mapping(Tree.Node => Clock.State) clocks; + mapping(Tree.Node => Machine.Hash) finalStates; + // matches existing in current tournament + mapping(Match.IdHash => Match.State) matches; + + // + // Events + // + + event matchCreated( + Tree.Node indexed one, + Tree.Node indexed two, + Tree.Node leftOfTwo + ); + + event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left); + + // + // Modifiers + // + + modifier tournamentNotFinished() { + require(!isFinished(), "tournament is finished"); + + _; + } + + modifier tournamentOpen() { + require(!isClosed(), "tournament check-in elapsed"); + + _; + } + + // + // Constructor + // + + constructor( + Machine.Hash _initialHash, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) { + initialHash = _initialHash; + startCycle = _startCycle; + level = _level; + startInstant = Time.currentTime(); + allowance = _allowance; + + if (_allowance.gt(ArbitrationConstants.CENSORSHIP_TOLERANCE)) { + maximumEnforceableDelay = Time.currentTime().add( + ArbitrationConstants.CENSORSHIP_TOLERANCE + ); + } else { + maximumEnforceableDelay = Time.currentTime().add(_allowance); + } + } + + // + // Methods + // + + /// @dev root tournaments are open to everyone, while non-root tournaments are open to anyone who's final state hash matches the one of the two in the tournament + function joinTournament( + Machine.Hash _finalState, + bytes32[] calldata _proof, + Tree.Node _leftNode, + Tree.Node _rightNode + ) external tournamentOpen { + Tree.Node _commitmentRoot = _leftNode.join(_rightNode); + + // Prove final state is in commitmentRoot + _commitmentRoot.proveFinalState(level, _finalState, _proof); + + // Verify whether finalState is one of the two allowed of tournament if nested + requireValidContestedFinalState(_finalState); + finalStates[_commitmentRoot] = _finalState; + + Clock.State storage _clock = clocks[_commitmentRoot]; + _clock.requireNotInitialized(); // reverts if commitment is duplicate + _clock.setNewPaused(startInstant, allowance); + + pairCommitment(_commitmentRoot, _clock, _leftNode, _rightNode); + } + + /// @notice Advance the match until the smallest divergence is found at current level + /// @dev this function is being called repeatedly in turns by the two parties that disagree on the commitment. + function advanceMatch( + Match.Id calldata _matchId, + Tree.Node _leftNode, + Tree.Node _rightNode, + Tree.Node _newLeftNode, + Tree.Node _newRightNode + ) external tournamentNotFinished { + Match.State storage _matchState = matches[_matchId.hashFromId()]; + _matchState.requireExist(); + _matchState.requireCanBeAdvanced(); + _matchState.requireParentHasChildren(_leftNode, _rightNode); + + if (!_matchState.agreesOnLeftNode(_leftNode)) { + // go down left in Commitment tree + _leftNode.requireChildren(_newLeftNode, _newRightNode); + _matchState.goDownLeftTree(_newLeftNode, _newRightNode); + } else { + // go down right in Commitment tree + _rightNode.requireChildren(_newLeftNode, _newRightNode); + _matchState.goDownRightTree(_newLeftNode, _newRightNode); + } + + // advance clocks + clocks[_matchId.commitmentOne].advanceClock(); + clocks[_matchId.commitmentTwo].advanceClock(); + + // TODO move event to lib? + emit matchAdvanced( + _matchId.hashFromId(), + _matchState.otherParent, + _matchState.leftNode + ); + } + + function winMatchByTimeout( + Match.Id calldata _matchId, + Tree.Node _leftNode, + Tree.Node _rightNode + ) external tournamentNotFinished { + matches[_matchId.hashFromId()].requireExist(); + Clock.State storage _clockOne = clocks[_matchId.commitmentOne]; + Clock.State storage _clockTwo = clocks[_matchId.commitmentTwo]; + + _clockOne.requireInitialized(); + _clockTwo.requireInitialized(); + + if (_clockOne.hasTimeLeft() && !_clockTwo.hasTimeLeft()) { + require( + _matchId.commitmentOne.verify(_leftNode, _rightNode), + "child nodes do not match parent (commitmentOne)" + ); + + _clockOne.addValidatorEffort(_clockTwo.timeSinceTimeout()); + pairCommitment( + _matchId.commitmentOne, + _clockOne, + _leftNode, + _rightNode + ); + } else if (!_clockOne.hasTimeLeft() && _clockTwo.hasTimeLeft()) { + require( + _matchId.commitmentTwo.verify(_leftNode, _rightNode), + "child nodes do not match parent (commitmentTwo)" + ); + + _clockTwo.addValidatorEffort(_clockOne.timeSinceTimeout()); + pairCommitment( + _matchId.commitmentTwo, + _clockTwo, + _leftNode, + _rightNode + ); + } else { + revert("cannot win by timeout"); + } + + delete matches[_matchId.hashFromId()]; + } + + function _tournamentWinner() internal view returns (Tree.Node) { + if (!isFinished()) { + return Tree.ZERO_NODE; + } + + ( + bool _hasDanglingCommitment, + Tree.Node _danglingCommitment + ) = hasDanglingCommitment(); + assert(_hasDanglingCommitment); + + return _danglingCommitment; + } + + /// @return _winner commitment of the tournament + function tournamentWinner() external view virtual returns (Tree.Node); + + // + // View methods + // + + function canWinMatchByTimeout( + Match.Id calldata _matchId + ) external view returns (bool) { + Clock.State memory _clockOne = clocks[_matchId.commitmentOne]; + Clock.State memory _clockTwo = clocks[_matchId.commitmentTwo]; + + return !_clockOne.hasTimeLeft() || !_clockTwo.hasTimeLeft(); + } + + function getCommitment( + Tree.Node _commitmentRoot + ) external view returns (Clock.State memory, Machine.Hash) { + return (clocks[_commitmentRoot], finalStates[_commitmentRoot]); + } + + function getMatch( + Match.IdHash _matchIdHash + ) public view returns (Match.State memory) { + return matches[_matchIdHash]; + } + + function getMatchCycle( + Match.IdHash _matchIdHash + ) external view returns (uint256) { + Match.State memory _m = getMatch(_matchIdHash); + return _m.toCycle(startCycle, level); + } + + function tournamentLevelConstants() + external + view + returns (uint64 _level, uint64 _log2step, uint64 _height) + { + _level = level; + _log2step = ArbitrationConstants.log2step(level); + _height = ArbitrationConstants.height(level); + } + + // + // Helper functions + // + + function hasDanglingCommitment() + internal + view + returns (bool _h, Tree.Node _node) + { + _node = danglingCommitment; + + if (!_node.isZero()) { + _h = true; + } + } + + function setDanglingCommitment(Tree.Node _node) internal { + danglingCommitment = _node; + } + + function clearDanglingCommitment() internal { + danglingCommitment = Tree.ZERO_NODE; + } + + function updateParentTournamentDelay(Time.Instant _delay) internal virtual; + + function setMaximumDelay(Time.Instant _delay) internal returns (bool) { + if (_delay.gt(maximumEnforceableDelay)) { + maximumEnforceableDelay = _delay; + return true; + } else { + return false; + } + } + + function pairCommitment( + Tree.Node _rootHash, + Clock.State memory _newClock, + Tree.Node _leftNode, + Tree.Node _rightNode + ) internal { + ( + bool _hasDanglingCommitment, + Tree.Node _danglingCommitment + ) = hasDanglingCommitment(); + + if (_hasDanglingCommitment) { + (Match.IdHash _matchId, Match.State memory _matchState) = Match + .createMatch( + _danglingCommitment, + _rootHash, + _leftNode, + _rightNode, + ArbitrationConstants.height(level) + ); + + matches[_matchId] = _matchState; + + Clock.State storage _firstClock = clocks[_danglingCommitment]; + Time.Instant _delay = Clock.deadline(_firstClock, _newClock); + Time.Duration _maxDuration = Clock.max(_firstClock, _newClock); + + setMaximumDelay(_delay); + updateParentTournamentDelay(_delay.add(_maxDuration)); // TODO hack + + _firstClock.advanceClock(); + + clearDanglingCommitment(); + updateParentTournamentDelay(_delay); + + emit matchCreated(_danglingCommitment, _rootHash, _leftNode); + } else { + updateParentTournamentDelay(maximumEnforceableDelay.add(_newClock.allowance)); + setDanglingCommitment(_rootHash); + } + } + + /// @return bool if _fianlState is allowed to join the tournament + function validContestedFinalState( + Machine.Hash _fianlState + ) internal view virtual returns (bool); + + function requireValidContestedFinalState( + Machine.Hash _finalState + ) internal view { + require( + validContestedFinalState(_finalState), + "tournament doesn't have contested final state" + ); + } + + /// @return bool if the tournament is still open to join + function isClosed() internal view returns (bool) { + if (allowance.gt(ArbitrationConstants.CENSORSHIP_TOLERANCE)) { + return + startInstant.timeoutElapsed( + ArbitrationConstants.CENSORSHIP_TOLERANCE + ); + } else { + return startInstant.timeoutElapsed(allowance); + } + } + + /// @return bool if the tournament is over + function isFinished() internal view returns (bool) { + return Time.currentTime().gt(maximumEnforceableDelay); + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol new file mode 100644 index 000000000..fb7fbd070 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/LeafTournament.sol"; +import "../abstracts/NonRootTournament.sol"; +import "step/contracts/interfaces/IUArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; + +/// @notice Bottom tournament of a multi-level instance +contract BottomTournament is LeafTournament, NonRootTournament { + constructor( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level, + IUArchState _stateInterface, + IUArchStep _stepInterface, + NonLeafTournament _parent + ) + LeafTournament(_stateInterface, _stepInterface) + NonRootTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + _parent + ) + {} +} diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol new file mode 100644 index 000000000..4107d1570 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/NonLeafTournament.sol"; +import "../abstracts/NonRootTournament.sol"; + +/// @notice Middle tournament is non-top, non-bottom of a multi-level instance +contract MiddleTournament is NonLeafTournament, NonRootTournament { + constructor( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level, + NonLeafTournament _parent + ) + NonLeafTournament(IInnerTournamentFactory(msg.sender)) + NonRootTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + _parent + ) + {} +} diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol new file mode 100644 index 000000000..00b238c15 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/RootTournament.sol"; +import "../abstracts/LeafTournament.sol"; +import "step/contracts/interfaces/IUArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; + +contract SingleLevelTournament is LeafTournament, RootTournament { + constructor( + Machine.Hash _initialHash, + IUArchState _stateInterface, + IUArchStep _stepInterface + ) + LeafTournament(_stateInterface, _stepInterface) + RootTournament(_initialHash) + {} +} diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol new file mode 100644 index 000000000..ea14567b6 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/RootTournament.sol"; +import "../abstracts/NonLeafTournament.sol"; + +/// @notice Top tournament of a multi-level instance +contract TopTournament is NonLeafTournament, RootTournament { + constructor( + IInnerTournamentFactory _innerFactory, + Machine.Hash _initialHash + ) + NonLeafTournament(_innerFactory) + RootTournament(_initialHash) + {} +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol new file mode 100644 index 000000000..fd60db430 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../interfaces/IInnerTournamentFactory.sol"; +import "../concretes/MiddleTournament.sol"; +import "../concretes/BottomTournament.sol"; +import "step/contracts/interfaces/IUArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; + +contract InnerTournamentFactory is IInnerTournamentFactory { + IUArchState immutable stateInterface; + IUArchStep immutable stepInterface; + + constructor(IUArchState _stateInterface, IUArchStep _stepInterface) { + stateInterface = _stateInterface; + stepInterface = _stepInterface; + } + + function instantiateInner( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external override returns (NonRootTournament) { + // the inner tournament is bottom tournament at last level + // else instantiate middle tournament + NonRootTournament _tournament; + if (_level == ArbitrationConstants.LEVELS - 1) { + _tournament = instantiateBottom( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level + ); + } else { + _tournament = instantiateMiddle( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level + ); + } + + return _tournament; + } + + function instantiateMiddle( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) internal returns (NonRootTournament) { + MiddleTournament _tournament = new MiddleTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + NonLeafTournament(msg.sender) + ); + + return _tournament; + } + + function instantiateBottom( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) internal returns (NonRootTournament) { + BottomTournament _tournament = new BottomTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + stateInterface, + stepInterface, + NonLeafTournament(msg.sender) + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol new file mode 100644 index 000000000..70f3fc13f --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../interfaces/IRootTournamentFactory.sol"; +import "../concretes/SingleLevelTournament.sol"; +import "../concretes/TopTournament.sol"; +import "step/contracts/interfaces/IUArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; + +contract RootTournamentFactory is IRootTournamentFactory { + IInnerTournamentFactory immutable innerFactory; + IUArchState immutable stateInterface; + IUArchStep immutable stepInterface; + + constructor( + IInnerTournamentFactory _innerFactory, + IUArchState _stateInterface, + IUArchStep _stepInterface + ) { + innerFactory = _innerFactory; + stateInterface = _stateInterface; + stepInterface = _stepInterface; + } + + function instantiateSingle( + Machine.Hash _initialHash + ) external override returns (RootTournament) { + SingleLevelTournament _tournament = new SingleLevelTournament( + _initialHash, + stateInterface, + stepInterface + ); + + emit rootCreated(_tournament); + + return _tournament; + } + + function instantiateTopOfMultiple( + Machine.Hash _initialHash + ) external override returns (RootTournament) { + TopTournament _tournament = new TopTournament( + innerFactory, + _initialHash + ); + + emit rootCreated(_tournament); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol new file mode 100644 index 000000000..1ca474c23 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9; + +import "../../Machine.sol"; +import "../../Tree.sol"; +import "../../Time.sol"; + +import "../abstracts/NonRootTournament.sol"; + +interface IInnerTournamentFactory { + function instantiateInner( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external returns (NonRootTournament); +} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol new file mode 100644 index 000000000..5f2fdc120 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9; + +import "../abstracts/RootTournament.sol"; +import "../abstracts/NonRootTournament.sol"; + +interface IMultiTournamentFactory { + event rootCreated(RootTournament); + + function instantiateTop( + Machine.Hash _initialHash + ) external returns (RootTournament); + + function instantiateInner( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external returns (NonRootTournament); +} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol new file mode 100644 index 000000000..ac383c3d5 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9; + +import "../interfaces/IInnerTournamentFactory.sol"; +import "../abstracts/RootTournament.sol"; + +interface IRootTournamentFactory { + event rootCreated(RootTournament); + + function instantiateSingle( + Machine.Hash _initialHash + ) external returns (RootTournament); + + function instantiateTopOfMultiple( + Machine.Hash _initialHash + ) external returns (RootTournament); +} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol new file mode 100644 index 000000000..759097921 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9; + +import "../abstracts/RootTournament.sol"; +import "../abstracts/NonRootTournament.sol"; + +interface ISingleTournamentFactory { + event rootCreated(RootTournament); + + function instantiateSingle( + Machine.Hash _initialHash + ) external returns (RootTournament); +} diff --git a/onchain/permissionless-arbitration/test/Counter.t.sol b/onchain/permissionless-arbitration/test/Counter.t.sol deleted file mode 100644 index 30235e8a8..000000000 --- a/onchain/permissionless-arbitration/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function testIncrement() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testSetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/onchain/permissionless-arbitration/test/Match.t.sol b/onchain/permissionless-arbitration/test/Match.t.sol new file mode 100644 index 000000000..49ce2287e --- /dev/null +++ b/onchain/permissionless-arbitration/test/Match.t.sol @@ -0,0 +1,162 @@ +// Copyright 2023 Cartesi Pte. Ltd. + +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +import "src/Match.sol"; +import "src/CanonicalConstants.sol"; + +pragma solidity ^0.8.0; + +contract MatchTest is Test { + using Tree for Tree.Node; + using Machine for Machine.Hash; + using Match for Match.Id; + using Match for Match.State; + + uint256 MAX_LOG2_SIZE = ArbitrationConstants.height(0); + + Match.State leftDivergenceMatch; + Match.State rightDivergenceMatch; + Match.IdHash leftDivergenceMatchId; + Match.IdHash rightDivergenceMatchId; + + function setUp() public { + Tree.Node leftDivergenceCommitment1 = Tree.ZERO_NODE.join( + Tree.ZERO_NODE + ); + Tree.Node rightDivergenceCommitment1 = Tree.ZERO_NODE.join( + Tree.ZERO_NODE + ); + + Tree.Node leftDivergenceCommitment2 = Tree + .Node + .wrap(bytes32(uint256(1))) + .join(Tree.ZERO_NODE); + Tree.Node rightDivergenceCommitment2 = Tree.ZERO_NODE.join( + Tree.Node.wrap(bytes32(uint256(1))) + ); + + (leftDivergenceMatchId, leftDivergenceMatch) = Match.createMatch( + leftDivergenceCommitment1, + leftDivergenceCommitment2, + Tree.Node.wrap(bytes32(uint256(1))), + Tree.ZERO_NODE, + 1 + ); + + (rightDivergenceMatchId, rightDivergenceMatch) = Match.createMatch( + rightDivergenceCommitment1, + rightDivergenceCommitment2, + Tree.ZERO_NODE, + Tree.Node.wrap(bytes32(uint256(1))), + 1 + ); + } + + function testDivergenceLeftWithEvenHeight() public { + assertTrue( + !leftDivergenceMatch.agreesOnLeftNode(Tree.ZERO_NODE), + "left node should diverge" + ); + ( + Machine.Hash _finalHashOne, + Machine.Hash _finalHashTwo + ) = leftDivergenceMatch.setDivergenceOnLeftLeaf(Tree.ZERO_NODE); + + leftDivergenceMatch.height = 2; + + assertTrue( + _finalHashOne.eq(Tree.ZERO_NODE.toMachineHash()), + "hash one should be zero" + ); + assertTrue( + _finalHashTwo.eq( + Tree.Node.wrap(bytes32(uint256(1))).toMachineHash() + ), + "hash two should be 1" + ); + } + + function testDivergenceRightWithEvenHeight() public { + assertTrue( + rightDivergenceMatch.agreesOnLeftNode(Tree.ZERO_NODE), + "left node should match" + ); + ( + Machine.Hash _finalHashOne, + Machine.Hash _finalHashTwo + ) = rightDivergenceMatch.setDivergenceOnRightLeaf(Tree.ZERO_NODE); + + rightDivergenceMatch.height = 2; + + assertTrue( + _finalHashOne.eq(Tree.ZERO_NODE.toMachineHash()), + "hash one should be zero" + ); + assertTrue( + _finalHashTwo.eq( + Tree.Node.wrap(bytes32(uint256(1))).toMachineHash() + ), + "hash two should be 1" + ); + } + + function testDivergenceLeftWithOddHeight() public { + assertTrue( + !leftDivergenceMatch.agreesOnLeftNode(Tree.ZERO_NODE), + "left node should diverge" + ); + ( + Machine.Hash _finalHashOne, + Machine.Hash _finalHashTwo + ) = leftDivergenceMatch.setDivergenceOnLeftLeaf(Tree.ZERO_NODE); + + leftDivergenceMatch.height = 3; + + assertTrue( + _finalHashOne.eq(Tree.ZERO_NODE.toMachineHash()), + "hash one should be zero" + ); + assertTrue( + _finalHashTwo.eq( + Tree.Node.wrap(bytes32(uint256(1))).toMachineHash() + ), + "hash two should be 1" + ); + } + + function testDivergenceRightWithOddHeight() public { + assertTrue( + rightDivergenceMatch.agreesOnLeftNode(Tree.ZERO_NODE), + "left node should match" + ); + ( + Machine.Hash _finalHashOne, + Machine.Hash _finalHashTwo + ) = rightDivergenceMatch.setDivergenceOnRightLeaf(Tree.ZERO_NODE); + + rightDivergenceMatch.height = 3; + + assertTrue( + _finalHashOne.eq(Tree.ZERO_NODE.toMachineHash()), + "hash one should be zero" + ); + assertTrue( + _finalHashTwo.eq( + Tree.Node.wrap(bytes32(uint256(1))).toMachineHash() + ), + "hash two should be 1" + ); + } +} diff --git a/onchain/permissionless-arbitration/test/MultiTournament.t.sol b/onchain/permissionless-arbitration/test/MultiTournament.t.sol new file mode 100644 index 000000000..0c5b2e0e5 --- /dev/null +++ b/onchain/permissionless-arbitration/test/MultiTournament.t.sol @@ -0,0 +1,392 @@ +// Copyright 2023 Cartesi Pte. Ltd. + +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +import "./Util.sol"; +import "src/tournament/factories/RootTournamentFactory.sol"; +import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/CanonicalConstants.sol"; +import "step/contracts/UArchStep.sol"; +import "step/contracts/UArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; +import "step/contracts/interfaces/IUArchState.sol"; + +pragma solidity ^0.8.0; + +contract MultiTournamentTest is Test { + using Tree for Tree.Node; + using Time for Time.Instant; + using Match for Match.Id; + using Machine for Machine.Hash; + + // players' commitment node at different height + // player 0, player 1, and player 2 + Tree.Node[][3] playerNodes; + + IUArchState immutable state; + IUArchStep immutable step; + IRootTournamentFactory immutable rootFactory; + IInnerTournamentFactory immutable innerFactory; + TopTournament topTournament; + MiddleTournament middleTournament; + + event matchCreated( + Tree.Node indexed one, + Tree.Node indexed two, + Tree.Node leftOfTwo + ); + event newInnerTournament(Match.IdHash indexed, NonRootTournament); + + constructor() { + state = new UArchState(); + step = new UArchStep(); + innerFactory = new InnerTournamentFactory(state, step); + rootFactory = new RootTournamentFactory(innerFactory, state, step); + } + + function setUp() public { + playerNodes[0] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + playerNodes[1] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + playerNodes[2] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + + playerNodes[0][0] = Tree.ZERO_NODE; + playerNodes[1][0] = Util.ONE_NODE; + playerNodes[2][0] = Util.ONE_NODE; + + for (uint256 _i = 1; _i <= ArbitrationConstants.height(0); _i++) { + // player 0 is all zero leaf node + playerNodes[0][_i] = playerNodes[0][_i - 1].join( + playerNodes[0][_i - 1] + ); + // player 1 is all 1 + playerNodes[1][_i] = playerNodes[1][_i - 1].join( + playerNodes[1][_i - 1] + ); + // player 2 is all 0 but right most leaf node is 1 + playerNodes[2][_i] = playerNodes[0][_i - 1].join( + playerNodes[2][_i - 1] + ); + } + } + + function testRootWinner() public { + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // no winner before tournament finished + Tree.Node _winner = topTournament.tournamentWinner(); + (bool _finished, Machine.Hash _finalState) = topTournament + .rootTournamentFinalState(); + + assertTrue(_winner.isZero(), "winner should be zero node"); + assertFalse(_finished, "tournament shouldn't be finished"); + assertTrue( + _finalState.eq(Machine.ZERO_STATE), + "final state should be zero" + ); + + // player 0 should win after fast forward time to tournament finishes + uint256 _t = block.timestamp; + uint256 _tournamentFinish = _t + + 1 + + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + + // the delay is doubled when a match is created + uint256 _tournamentFinishWithMatch = _tournamentFinish + + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + + vm.warp(_tournamentFinish); + _winner = topTournament.tournamentWinner(); + (_finished, _finalState) = topTournament.rootTournamentFinalState(); + + assertTrue( + _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), + "winner should be player 0" + ); + assertTrue(_finished, "tournament should be finished"); + assertTrue( + _finalState.eq(Tree.ZERO_NODE.toMachineHash()), + "final state should be zero" + ); + + // rewind time in half and pair commitment, expect a match + vm.warp(_t); + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + // no dangling commitment available, should revert + vm.warp(_tournamentFinishWithMatch); + vm.expectRevert(); + _winner = topTournament.tournamentWinner(); + } + + function testInner() public { + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // pair commitment, expect a match + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + Match.Id memory _matchId = Util.matchId(playerNodes, 1, 0); + + // advance match to end, this match will always advance to left tree + uint256 _playerToSeal = Util.advanceMatch01AtLevel( + playerNodes, + topTournament, + _matchId, + 0 + ); + + // seal match + topTournament.sealInnerMatchAndCreateInnerTournament( + _matchId, + playerNodes[_playerToSeal][0], + playerNodes[_playerToSeal][0], + Machine.ZERO_STATE, + Util.generateProof( + playerNodes, + _playerToSeal, + ArbitrationConstants.height(1) + ) + ); + + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // pair commitment, expect a match + // player 2 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 2); + + _matchId = Util.matchId(playerNodes, 2, 0); + + // advance match to end, this match will always advance to right tree + _playerToSeal = Util.advanceMatch02AtLevel( + playerNodes, + topTournament, + _matchId, + 0 + ); + + // seal match + topTournament.sealInnerMatchAndCreateInnerTournament( + _matchId, + playerNodes[0][0], + playerNodes[_playerToSeal][0], + Machine.ZERO_STATE, + Util.generateProof( + playerNodes, + _playerToSeal, + ArbitrationConstants.height(1) + ) + ); + } + + function testInnerWinner() public { + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // pair commitment, expect a match + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + Match.Id memory _matchId = Util.matchId(playerNodes, 1, 0); + + // advance match to end, this match will always advance to left tree + uint256 _playerToSeal = Util.advanceMatch01AtLevel( + playerNodes, + topTournament, + _matchId, + 0 + ); + + // expect new inner created + vm.recordLogs(); + + // seal match + topTournament.sealInnerMatchAndCreateInnerTournament( + _matchId, + playerNodes[_playerToSeal][0], + playerNodes[_playerToSeal][0], + Machine.ZERO_STATE, + Util.generateProof( + playerNodes, + _playerToSeal, + ArbitrationConstants.height(1) + ) + ); + + Vm.Log[] memory _entries = vm.getRecordedLogs(); + assertEq(_entries[0].topics.length, 2); + assertEq( + _entries[0].topics[0], + keccak256("newInnerTournament(bytes32,address)") + ); + assertEq( + _entries[0].topics[1], + Match.IdHash.unwrap(_matchId.hashFromId()) + ); + + middleTournament = MiddleTournament( + address(bytes20(bytes32(_entries[0].data) << (12 * 8))) + ); + + Tree.Node _winner = middleTournament.tournamentWinner(); + assertTrue(_winner.isZero(), "winner should be zero node"); + + // player 0 should win after fast forward time to inner tournament finishes + uint256 _t = block.timestamp; + // the delay is doubled when a match is created + uint256 _rootTournamentFinish = _t + + 2 * + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + Util.joinMiddleTournament(playerNodes, middleTournament, 0, 1); + + vm.warp(_rootTournamentFinish - 1); + _winner = middleTournament.tournamentWinner(); + topTournament.winInnerMatch( + middleTournament, + playerNodes[0][ArbitrationConstants.height(0) - 1], + playerNodes[0][ArbitrationConstants.height(0) - 1] + ); + + vm.warp(_rootTournamentFinish + 1); + (bool _finished, Machine.Hash _finalState) = topTournament + .rootTournamentFinalState(); + + assertTrue( + _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), + "winner should be player 0" + ); + assertTrue(_finished, "tournament should be finished"); + assertTrue( + _finalState.eq(Tree.ZERO_NODE.toMachineHash()), + "final state should be zero" + ); + + //create another tournament for other test + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // pair commitment, expect a match + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + _matchId = Util.matchId(playerNodes, 1, 0); + + // advance match to end, this match will always advance to left tree + _playerToSeal = Util.advanceMatch01AtLevel( + playerNodes, + topTournament, + _matchId, + 0 + ); + + // expect new inner created + vm.recordLogs(); + + // seal match + topTournament.sealInnerMatchAndCreateInnerTournament( + _matchId, + playerNodes[_playerToSeal][0], + playerNodes[_playerToSeal][0], + Machine.ZERO_STATE, + Util.generateProof( + playerNodes, + _playerToSeal, + ArbitrationConstants.height(1) + ) + ); + + _entries = vm.getRecordedLogs(); + assertEq(_entries[0].topics.length, 2); + assertEq( + _entries[0].topics[0], + keccak256("newInnerTournament(bytes32,address)") + ); + assertEq( + _entries[0].topics[1], + Match.IdHash.unwrap(_matchId.hashFromId()) + ); + + middleTournament = MiddleTournament( + address(bytes20(bytes32(_entries[0].data) << (12 * 8))) + ); + + _winner = middleTournament.tournamentWinner(); + assertTrue(_winner.isZero(), "winner should be zero node"); + + _t = block.timestamp; + // the delay is doubled when a match is created + uint256 _middleTournamentFinish = _t + + 1 + + 2 * + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + _rootTournamentFinish = + _middleTournamentFinish + + 2 * + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + + Util.joinMiddleTournament(playerNodes, middleTournament, 0, 1); + + //let player 1 join, then timeout player 0 + Util.joinMiddleTournament(playerNodes, middleTournament, 1, 1); + + (Clock.State memory _player0Clock, ) = middleTournament.getCommitment( + playerNodes[0][ArbitrationConstants.height(1)] + ); + vm.warp( + Time.Instant.unwrap( + _player0Clock.startInstant.add(_player0Clock.allowance) + ) + ); + _matchId = Util.matchId(playerNodes, 1, 1); + middleTournament.winMatchByTimeout( + _matchId, + playerNodes[1][ArbitrationConstants.height(1) - 1], + playerNodes[1][ArbitrationConstants.height(1) - 1] + ); + + vm.warp(_middleTournamentFinish); + _winner = middleTournament.tournamentWinner(); + topTournament.winInnerMatch( + middleTournament, + playerNodes[1][ArbitrationConstants.height(0) - 1], + playerNodes[1][ArbitrationConstants.height(0) - 1] + ); + + vm.warp(_rootTournamentFinish); + (_finished, _finalState) = topTournament.rootTournamentFinalState(); + + assertTrue( + _winner.eq(playerNodes[1][ArbitrationConstants.height(0)]), + "winner should be player 1" + ); + assertTrue(_finished, "tournament should be finished"); + assertTrue( + _finalState.eq(Util.ONE_NODE.toMachineHash()), + "final state should be 1" + ); + } +} diff --git a/onchain/permissionless-arbitration/test/Tournament.t.sol b/onchain/permissionless-arbitration/test/Tournament.t.sol new file mode 100644 index 000000000..6b49ea838 --- /dev/null +++ b/onchain/permissionless-arbitration/test/Tournament.t.sol @@ -0,0 +1,218 @@ +// Copyright 2023 Cartesi Pte. Ltd. + +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +import "./Util.sol"; +import "src/tournament/factories/RootTournamentFactory.sol"; +import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/CanonicalConstants.sol"; +import "step/contracts/UArchStep.sol"; +import "step/contracts/UArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; +import "step/contracts/interfaces/IUArchState.sol"; + +pragma solidity ^0.8.0; + +contract TournamentTest is Test { + using Tree for Tree.Node; + using Time for Time.Instant; + using Match for Match.Id; + using Machine for Machine.Hash; + + // players' commitment node at different height + // player 0, player 1, and player 2 + Tree.Node[][3] playerNodes; + Tree.Node constant ONE_NODE = Tree.Node.wrap(bytes32(uint256(1))); + + IUArchState immutable state; + IUArchStep immutable step; + IRootTournamentFactory immutable rootFactory; + IInnerTournamentFactory immutable innerFactory; + TopTournament topTournament; + MiddleTournament middleTournament; + + event matchCreated( + Tree.Node indexed one, + Tree.Node indexed two, + Tree.Node leftOfTwo + ); + event newInnerTournament(Match.IdHash indexed, NonRootTournament); + + constructor() { + state = new UArchState(); + step = new UArchStep(); + innerFactory = new InnerTournamentFactory(state, step); + rootFactory = new RootTournamentFactory(innerFactory, state, step); + } + + function setUp() public { + playerNodes[0] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + playerNodes[1] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + playerNodes[2] = new Tree.Node[](ArbitrationConstants.height(0) + 1); + + playerNodes[0][0] = Tree.ZERO_NODE; + playerNodes[1][0] = ONE_NODE; + playerNodes[2][0] = ONE_NODE; + + for (uint256 _i = 1; _i <= ArbitrationConstants.height(0); _i++) { + // player 0 is all zero leaf node + playerNodes[0][_i] = playerNodes[0][_i - 1].join( + playerNodes[0][_i - 1] + ); + // player 1 is all 1 + playerNodes[1][_i] = playerNodes[1][_i - 1].join( + playerNodes[1][_i - 1] + ); + // player 2 is all 0 but right most leaf node is 1 + playerNodes[2][_i] = playerNodes[0][_i - 1].join( + playerNodes[2][_i - 1] + ); + } + } + + function testJoinTournament() public { + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + // duplicate commitment should be reverted + vm.expectRevert("clock is initialized"); + Util.joinTopTournament(playerNodes, topTournament, 0); + + // pair commitment, expect a match + vm.expectEmit(true, true, false, true, address(topTournament)); + emit matchCreated( + playerNodes[0][ArbitrationConstants.height(0) - 0], + playerNodes[1][ArbitrationConstants.height(0) - 0], + playerNodes[1][ArbitrationConstants.height(0) - 1] + ); + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + } + + function testTimeout() public { + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + + uint256 _t = block.timestamp; + // the delay is doubled when a match is created + uint256 _tournamentFinishWithMatch = _t + + 1 + + 2 * + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + Match.Id memory _matchId = Util.matchId(playerNodes, 1, 0); + assertFalse( + topTournament.canWinMatchByTimeout(_matchId), + "shouldn't be able to win match by timeout" + ); + + // player 1 should win after fast forward time to player 0 timeout + // player 0 timeout first because he's supposed to advance match first after the match is created + (Clock.State memory _player0Clock, ) = topTournament.getCommitment( + playerNodes[0][ArbitrationConstants.height(0)] + ); + vm.warp( + Time.Instant.unwrap( + _player0Clock.startInstant.add(_player0Clock.allowance) + ) + ); + assertTrue( + topTournament.canWinMatchByTimeout(_matchId), + "should be able to win match by timeout" + ); + topTournament.winMatchByTimeout( + _matchId, + playerNodes[1][ArbitrationConstants.height(0) - 1], + playerNodes[1][ArbitrationConstants.height(0) - 1] + ); + + vm.warp(_tournamentFinishWithMatch); + Tree.Node _winner = topTournament.tournamentWinner(); + (bool _finished, Machine.Hash _finalState) = topTournament + .rootTournamentFinalState(); + + assertTrue( + _winner.eq(playerNodes[1][ArbitrationConstants.height(0)]), + "winner should be player 1" + ); + assertTrue(_finished, "tournament should be finished"); + assertTrue(_finalState.eq(Util.ONE_STATE), "final state should be 1"); + + topTournament = Util.initializePlayer0Tournament( + playerNodes, + rootFactory + ); + _t = block.timestamp; + + // the delay is doubled when a match is created + _tournamentFinishWithMatch = + _t + + 1 + + 2 * + Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); + + // player 1 joins tournament + Util.joinTopTournament(playerNodes, topTournament, 1); + + // player 0 should win after fast forward time to player 1 timeout + // player 1 timeout first because he's supposed to advance match after player 0 advanced + _matchId = Util.matchId(playerNodes, 1, 0); + + topTournament.advanceMatch( + _matchId, + playerNodes[0][ArbitrationConstants.height(0) - 1], + playerNodes[0][ArbitrationConstants.height(0) - 1], + playerNodes[0][ArbitrationConstants.height(0) - 2], + playerNodes[0][ArbitrationConstants.height(0) - 2] + ); + (Clock.State memory _player1Clock, ) = topTournament.getCommitment( + playerNodes[1][ArbitrationConstants.height(0)] + ); + vm.warp( + Time.Instant.unwrap( + _player1Clock.startInstant.add(_player1Clock.allowance) + ) + ); + assertTrue( + topTournament.canWinMatchByTimeout(_matchId), + "should be able to win match by timeout" + ); + topTournament.winMatchByTimeout( + _matchId, + playerNodes[0][ArbitrationConstants.height(0) - 1], + playerNodes[0][ArbitrationConstants.height(0) - 1] + ); + + vm.warp(_tournamentFinishWithMatch); + _winner = topTournament.tournamentWinner(); + (_finished, _finalState) = topTournament.rootTournamentFinalState(); + + assertTrue( + _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), + "winner should be player 0" + ); + assertTrue(_finished, "tournament should be finished"); + assertTrue( + _finalState.eq(Tree.ZERO_NODE.toMachineHash()), + "final state should be zero" + ); + } +} diff --git a/onchain/permissionless-arbitration/test/TournamentFactory.t.sol b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol new file mode 100644 index 000000000..d95490b71 --- /dev/null +++ b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol @@ -0,0 +1,77 @@ +// Copyright 2023 Cartesi Pte. Ltd. + +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +import "src/tournament/abstracts/RootTournament.sol"; +import "src/tournament/factories/RootTournamentFactory.sol"; +import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/CanonicalConstants.sol"; +import "step/contracts/UArchStep.sol"; +import "step/contracts/UArchState.sol"; +import "step/contracts/interfaces/IUArchStep.sol"; +import "step/contracts/interfaces/IUArchState.sol"; + +pragma solidity ^0.8.0; + +contract TournamentFactoryTest is Test { + IRootTournamentFactory rootFactory; + IInnerTournamentFactory innerFactory; + IUArchState state = new UArchState(); + IUArchStep step = new UArchStep(); + + function setUp() public { + innerFactory = new InnerTournamentFactory(state, step); + rootFactory = new RootTournamentFactory(innerFactory, state, step); + } + + function testRootTournament() public { + RootTournament rootTournament = rootFactory.instantiateSingle( + Machine.ZERO_STATE + ); + + (uint64 _level, uint64 _log2step, uint64 _height) = rootTournament + .tournamentLevelConstants(); + + assertEq(_level, 0, "level should be 0"); + assertEq( + _log2step, + ArbitrationConstants.log2step(_level), + "log2step should match" + ); + assertEq( + _height, + ArbitrationConstants.height(_level), + "height should match" + ); + + rootTournament = rootFactory.instantiateTopOfMultiple( + Machine.ZERO_STATE + ); + + (_level, _log2step, _height) = rootTournament + .tournamentLevelConstants(); + + assertEq(_level, 0, "level should be 0"); + assertEq( + _log2step, + ArbitrationConstants.log2step(_level), + "log2step should match" + ); + assertEq( + _height, + ArbitrationConstants.height(_level), + "height should match" + ); + } +} diff --git a/onchain/permissionless-arbitration/test/Util.sol b/onchain/permissionless-arbitration/test/Util.sol new file mode 100644 index 000000000..2731414a9 --- /dev/null +++ b/onchain/permissionless-arbitration/test/Util.sol @@ -0,0 +1,218 @@ +// Copyright 2023 Cartesi Pte. Ltd. + +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +import "src/Match.sol"; +import "src/CanonicalConstants.sol"; +import "src/tournament/concretes/TopTournament.sol"; +import "src/tournament/concretes/MiddleTournament.sol"; +import "src/tournament/interfaces/IRootTournamentFactory.sol"; + +pragma solidity ^0.8.0; + +library Util { + using Tree for Tree.Node; + using Machine for Machine.Hash; + using Match for Match.Id; + using Match for Match.State; + + Tree.Node constant ONE_NODE = Tree.Node.wrap(bytes32(uint256(1))); + Machine.Hash constant ONE_STATE = Machine.Hash.wrap(bytes32(uint256(1))); + Machine.Hash constant TWO_STATE = Machine.Hash.wrap(bytes32(uint256(2))); + + function generateProof( + Tree.Node[][3] memory _playerNodes, + uint256 _player, + uint64 _height + ) internal pure returns (bytes32[] memory) { + bytes32[] memory _proof = new bytes32[](_height); + for (uint64 _i = 0; _i < _height; _i++) { + _proof[_i] = Tree.Node.unwrap(_playerNodes[_player][_i]); + } + return _proof; + } + + // advance match between player 0 and player 1 + function advanceMatch01AtLevel( + Tree.Node[][3] memory _playerNodes, + TopTournament _topTournament, + Match.Id memory _matchId, + uint64 _level + ) internal returns (uint256 _playerToSeal) { + uint256 _current = ArbitrationConstants.height(_level); + for (_current; _current > 1; _current -= 1) { + if (_playerToSeal == 0) { + // advance match alternately until it can be sealed + // starts with player 0 + _topTournament.advanceMatch( + _matchId, + _playerNodes[0][_current - 1], + _playerNodes[0][_current - 1], + _playerNodes[0][_current - 2], + _playerNodes[0][_current - 2] + ); + _playerToSeal = 1; + } else { + _topTournament.advanceMatch( + _matchId, + _playerNodes[1][_current - 1], + _playerNodes[1][_current - 1], + _playerNodes[1][_current - 2], + _playerNodes[1][_current - 2] + ); + _playerToSeal = 0; + } + } + } + + // advance match between player 0 and player 2 + function advanceMatch02AtLevel( + Tree.Node[][3] memory _playerNodes, + TopTournament _topTournament, + Match.Id memory _matchId, + uint64 _level + ) internal returns (uint256 _playerToSeal) { + uint256 _current = ArbitrationConstants.height(_level); + for (_current; _current > 1; _current -= 1) { + if (_playerToSeal == 0) { + // advance match alternately until it can be sealed + // starts with player 0 + _topTournament.advanceMatch( + _matchId, + _playerNodes[0][_current - 1], + _playerNodes[0][_current - 1], + _playerNodes[0][_current - 2], + _playerNodes[0][_current - 2] + ); + _playerToSeal = 2; + } else { + _topTournament.advanceMatch( + _matchId, + _playerNodes[0][_current - 1], + _playerNodes[2][_current - 1], + _playerNodes[0][_current - 2], + _playerNodes[2][_current - 2] + ); + _playerToSeal = 0; + } + } + } + + // create new _topTournament and player 0 joins it + function initializePlayer0Tournament( + Tree.Node[][3] memory _playerNodes, + IRootTournamentFactory _rootFactory + ) internal returns (TopTournament _topTournament) { + _topTournament = TopTournament( + address(_rootFactory.instantiateTopOfMultiple(Machine.ZERO_STATE)) + ); + // player 0 joins tournament + joinTopTournament(_playerNodes, _topTournament, 0); + } + + // _player joins _topTournament + function joinTopTournament( + Tree.Node[][3] memory _playerNodes, + TopTournament _topTournament, + uint256 _player + ) internal { + if (_player == 0) { + _topTournament.joinTournament( + Machine.ZERO_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(0) + ), + _playerNodes[0][ArbitrationConstants.height(0) - 1], + _playerNodes[0][ArbitrationConstants.height(0) - 1] + ); + } else if (_player == 1) { + _topTournament.joinTournament( + ONE_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(0) + ), + _playerNodes[1][ArbitrationConstants.height(0) - 1], + _playerNodes[1][ArbitrationConstants.height(0) - 1] + ); + } else if (_player == 2) { + _topTournament.joinTournament( + TWO_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(0) + ), + _playerNodes[0][ArbitrationConstants.height(0) - 1], + _playerNodes[2][ArbitrationConstants.height(0) - 1] + ); + } + } + + // _player joins _middleTournament at _level + function joinMiddleTournament( + Tree.Node[][3] memory _playerNodes, + MiddleTournament _middleTournament, + uint256 _player, + uint64 _level + ) internal { + if (_player == 0) { + _middleTournament.joinTournament( + Machine.ZERO_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(_level) + ), + _playerNodes[0][ArbitrationConstants.height(_level) - 1], + _playerNodes[0][ArbitrationConstants.height(_level) - 1] + ); + } else if (_player == 1) { + _middleTournament.joinTournament( + ONE_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(_level) + ), + _playerNodes[1][ArbitrationConstants.height(_level) - 1], + _playerNodes[1][ArbitrationConstants.height(_level) - 1] + ); + } else if (_player == 2) { + _middleTournament.joinTournament( + TWO_STATE, + generateProof( + _playerNodes, + _player, + ArbitrationConstants.height(_level) + ), + _playerNodes[0][ArbitrationConstants.height(_level) - 1], + _playerNodes[2][ArbitrationConstants.height(_level) - 1] + ); + } + } + + // create match id for player 0 and _opponent at _level + function matchId( + Tree.Node[][3] memory _playerNodes, + uint256 _opponent, + uint64 _level + ) internal pure returns (Match.Id memory) { + return + Match.Id( + _playerNodes[0][ArbitrationConstants.height(_level)], + _playerNodes[_opponent][ArbitrationConstants.height(_level)] + ); + } +} From 9fed19d2178d148ee1423e78fe2a6bd1c7781dcf Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Mon, 19 Jun 2023 06:56:39 -0300 Subject: [PATCH 03/25] feat(arbitration): create cryptographic primitives --- .../offchain/.dockerignore | 2 + .../offchain/.luarc.json | 13 ++ .../offchain/Dockerfile | 10 ++ .../offchain/README.md | 36 +++++ .../offchain/cryptography/hash.lua | 97 ++++++++++++ .../offchain/cryptography/merkle_builder.lua | 138 ++++++++++++++++++ .../offchain/cryptography/merkle_tree.lua | 30 ++++ .../offchain/utils/arithmetic.lua | 47 ++++++ 8 files changed, 373 insertions(+) create mode 100644 onchain/permissionless-arbitration/offchain/.dockerignore create mode 100644 onchain/permissionless-arbitration/offchain/.luarc.json create mode 100644 onchain/permissionless-arbitration/offchain/Dockerfile create mode 100644 onchain/permissionless-arbitration/offchain/README.md create mode 100644 onchain/permissionless-arbitration/offchain/cryptography/hash.lua create mode 100644 onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua create mode 100644 onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/arithmetic.lua diff --git a/onchain/permissionless-arbitration/offchain/.dockerignore b/onchain/permissionless-arbitration/offchain/.dockerignore new file mode 100644 index 000000000..7e99db842 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/.dockerignore @@ -0,0 +1,2 @@ +program/simple-linux-program/ +program/simple-program/ diff --git a/onchain/permissionless-arbitration/offchain/.luarc.json b/onchain/permissionless-arbitration/offchain/.luarc.json new file mode 100644 index 000000000..5f8d0ef78 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/.luarc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + + "runtime.version": "Lua 5.4", + + "diagnostics": { + "enable": true + }, + + "workspace.library": { + "runtime/lua": true + } +} diff --git a/onchain/permissionless-arbitration/offchain/Dockerfile b/onchain/permissionless-arbitration/offchain/Dockerfile new file mode 100644 index 000000000..d5a88a2c4 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/Dockerfile @@ -0,0 +1,10 @@ +FROM --platform=linux/amd64 diegonehab/machine-emulator +RUN apt-get -y update; apt-get -y install curl +RUN curl -sSL https://github.com/foundry-rs/foundry/releases/download/nightly/foundry_nightly_linux_$(dpkg --print-architecture).tar.gz | \ + tar -zx -C /usr/local/bin + +ADD ./ ./src/ +WORKDIR "./src" + +RUN chmod +x ./entrypoint.lua +ENTRYPOINT ["./entrypoint.lua"] diff --git a/onchain/permissionless-arbitration/offchain/README.md b/onchain/permissionless-arbitration/offchain/README.md new file mode 100644 index 000000000..4d49bcb32 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/README.md @@ -0,0 +1,36 @@ +# Permissionless Arbitration (NxN) Lua prototype Node + +## Run example + +``` +docker build -t nxn_playground:latest . && \ +docker run --platform linux/amd64 --rm nxn_playground:latest +``` + +## Generate programs + +``` +cd program +``` + +``` +docker run --platform linux/amd64 -it --rm -h playground \ + -e USER=$(id -u -n) \ + -e GROUP=$(id -g -n) \ + -e UID=$(id -u) \ + -e GID=$(id -g) \ + -v (pwd):/home/$(id -u -n) \ + -w /home/$(id -u -n) \ + diegonehab/playground:develop /bin/bash -c "./gen_machine_linux.sh" +``` + +``` +docker run --platform linux/amd64 -it --rm -h playground \ + -e USER=$(id -u -n) \ + -e GROUP=$(id -g -n) \ + -e UID=$(id -u) \ + -e GID=$(id -g) \ + -v (pwd):/home/$(id -u -n) \ + -w /home/$(id -u -n) \ + diegonehab/playground:develop /bin/bash -c "./gen_machine_simple.sh" +``` diff --git a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua new file mode 100644 index 000000000..fff87c355 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua @@ -0,0 +1,97 @@ +local keccak_bin = require "cartesi".keccak + +local function hex_from_bin(bin) + assert(bin:len() == 32) + return "0x" .. string.gsub(bin, ".", function(c) + return string.format("%02x", string.byte(c)) + end) +end + +local function keccak(...) + return hex_from_bin(keccak_bin(...)) +end + +local internalized_hahes = {} +local iterateds = {} + +local Hash = {} +Hash.__index = Hash + +function Hash:from_digest(digest_hex) + assert(type(digest_hex) == "string", digest_hex:len() == 66) + + local x = internalized_hahes[digest_hex] + if x then return x end + + local h = {digest_hex = digest_hex} + iterateds[h] = {h} + setmetatable(h, self) + internalized_hahes[digest_hex] = h + return h +end + +function Hash:from_digest_bin(digest_bin) + local digest_hex = hex_from_bin(digest_bin) + return self:from_digest(digest_hex) +end + +function Hash:from_data(data) + local digest_hex = keccak(data) + return self:from_digest(digest_hex) +end + +function Hash:join(other_hash) + assert(getmetatable(other_hash) == Hash) + local digest_hex = keccak(self.digest_hex, other_hash.digest_hex) + local ret = Hash:from_digest(digest_hex) + ret.left = self.digest_hex + ret.right = other_hash.digest_hex + return ret +end + +function Hash:children() + local left, right= self.left, self.right + if left and right then + return true, left, right + else + return false + end +end + +function Hash:iterated_merkle(level) + level = level + 1 + local iterated = iterateds[self] + + local ret = iterated[level] + if ret then return ret end + + local i = #iterated -- at least 1 + local highest_level = iterated[i] + while i < level do + highest_level = highest_level:join(highest_level) + i = i + 1 + iterated[i] = highest_level + end + + return highest_level +end + +Hash.__tostring = function (x) + return x.digest_hex +end + +local zero_bytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000" +local zero_hash = Hash:from_digest(zero_bytes32) + +Hash.zero = zero_hash + +function Hash:is_zero() + return self == zero_hash +end + + +function Hash:is_of_type_hash(x) + return getmetatable(x) == self +end + +return Hash diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua new file mode 100644 index 000000000..09c360c58 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua @@ -0,0 +1,138 @@ +local MerkleTree = require "cryptography.merkle_tree" +local arithmetic = require "utils.arithmetic" + +local ulte = arithmetic.ulte +local semi_sum = arithmetic.semi_sum + +local Slice = {} +Slice.__index = Slice + +function Slice:new(arr, start_idx_inc, end_idx_ex) + start_idx_inc = start_idx_inc or 1 + end_idx_ex = end_idx_ex or (#arr + 1) + assert(start_idx_inc > 0) + assert(ulte(start_idx_inc, end_idx_ex)) + assert(end_idx_ex <= #arr + 1) + local s = { + arr = arr, + start_idx_inc = start_idx_inc, + end_idx_ex = end_idx_ex, + } + setmetatable(s, self) + return s +end + +function Slice:slice(si, ei) + assert(si > 0) + assert(ulte(si, ei)) + local start_idx_inc = self.start_idx_inc + si - 1 + local end_idx_ex = self.start_idx_inc + ei - 1 + assert(ulte(end_idx_ex, self.end_idx_ex)) + return Slice:new(self.arr, start_idx_inc, end_idx_ex) +end + +function Slice:len() + return self.end_idx_ex - self.start_idx_inc +end + +function Slice:get(idx) + idx = assert(math.tointeger(idx)) + assert(idx > 0) + local i = self.start_idx_inc + idx - 1 + assert(i < self.end_idx_ex) + return self.arr[i] +end + +function Slice:find_cell_containing(elem) + local l, r = 1, self:len() + + while math.ult(l, r) do + local m = semi_sum(l, r) + if math.ult(self:get(m).accumulated_count - 1, elem - 1) then + l = m + 1 + else + r = m + end + end + + return l +end + + +local MerkleBuilder = {} +MerkleBuilder.__index = MerkleBuilder + +function MerkleBuilder:new() + local m = { + leafs = {}, + } + setmetatable(m, self) + return m +end + +function MerkleBuilder:add(hash, rep) + rep = rep or 1 + assert(math.ult(0, rep)) + + local last = self.leafs[#self.leafs] + if last then + assert(last.accumulated_count ~= 0, "merkle builder is full") + local accumulated_count = rep + last.accumulated_count + + if not math.ult(rep, accumulated_count) then -- overflow... + assert(accumulated_count == 0) -- then it has to be zero, and nothing else can fit. + end + + table.insert(self.leafs, {hash = hash, accumulated_count = accumulated_count}) + else + table.insert(self.leafs, {hash = hash, accumulated_count = rep}) + end +end + +local function merkle(leafs, log2size, stride) + local first_time = stride * (1 << log2size) + 1 + local last_time = (stride + 1) * (1 << log2size) + + local first_cell = leafs:find_cell_containing(first_time) + local last_cell = leafs:find_cell_containing(last_time) + + if first_cell == last_cell then + return leafs:get(first_cell).hash:iterated_merkle(log2size) + end + + local slice = leafs:slice(first_cell, last_cell + 1) + local hash_left = merkle(slice, log2size - 1, stride << 1) + local hash_right = merkle(slice, log2size - 1, (stride << 1) + 1) + + return hash_left:join(hash_right) +end + +function MerkleBuilder:build() + local last = assert(self.leafs[#self.leafs], #self.leafs) + local count = last.accumulated_count + + local log2size + if count == 0 then + log2size = 64 + else + assert(arithmetic.is_pow2(count), count) + log2size = arithmetic.ctz(count) + end + + local root_hash = merkle(Slice:new(self.leafs), log2size, 0) + return MerkleTree:new(self.leafs, root_hash, log2size) +end + +-- local Hash = require "cryptography.hash" +-- local builder = MerkleBuilder:new() +-- builder:add(Hash.zero, 2) +-- builder:add(Hash.zero) +-- builder:add(Hash.zero) +-- builder:add(Hash.zero, 3) +-- builder:add(Hash.zero) +-- builder:add(Hash.zero, 0 - 8) +-- print(builder:build().root_hash) + +-- print(Hash.zero:iterated_merkle(64)) + +return MerkleBuilder diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua new file mode 100644 index 000000000..84cece02c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua @@ -0,0 +1,30 @@ +local MerkleTree = {} +MerkleTree.__index = MerkleTree + +function MerkleTree:new(leafs, root_hash, log2size) + local m = { + leafs = leafs, + root_hash = root_hash, + digest_hex = root_hash.digest_hex, + log2size = log2size, + } + setmetatable(m, self) + return m +end + +function MerkleTree:join(other_hash) + return self.root_hash:join(other_hash) +end + +function MerkleTree:children() + return self.root_hash:children() +end + +function MerkleTree:iterated_merkle(level) + return self.root_hash:iterated_merkle(level) +end + +-- TODO add generate proof. +-- TODO add children?? + +return MerkleTree diff --git a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua new file mode 100644 index 000000000..44c0b1c33 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua @@ -0,0 +1,47 @@ +local function max_int(k) + assert(k <= 64) + return (1 << k) - 1 +end + +local function ulte(x, y) + return x == y or math.ult(x, y) +end + +local function is_pow2(x) + return (x & (x-1)) == 0 +end + +-- Returns number of leading zeroes of x. Shamelessly stolen from the book +-- Hacker's Delight. +local function clz(x) + if x == 0 then return 64 end + local n = 0 + if (x & 0xFFFFFFFF00000000) == 0 then n = n + 32; x = x << 32 end + if (x & 0xFFFF000000000000) == 0 then n = n + 16; x = x << 16 end + if (x & 0xFF00000000000000) == 0 then n = n + 8; x = x << 8 end + if (x & 0xF000000000000000) == 0 then n = n + 4; x = x << 4 end + if (x & 0xC000000000000000) == 0 then n = n + 2; x = x << 2 end + if (x & 0x8000000000000000) == 0 then n = n + 1 end + return n +end + +-- Returns number of trailing zeroes of x. Shamelessly stolen from the book +-- Hacker's Delight. +local function ctz(x) + x = x & (~x + 1) + return 63 - clz(x) +end + +local function semi_sum(a, b) + assert(ulte(a, b)) + return a + (b - a) // 2 +end + +return { + max_int = max_int, + ulte = ulte, + is_pow2 = is_pow2, + clz = clz, + ctz = ctz, + semi_sum = semi_sum, +} From d999af7a552419bc8f7c9088e319bf7e81868964 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Wed, 5 Jul 2023 20:44:30 -0300 Subject: [PATCH 04/25] feat(arbitration): add computation hash generation --- .../offchain/README.md | 31 +---- .../offchain/computation/commitment.lua | 108 ++++++++++++++++++ .../offchain/computation/machine.lua | 98 ++++++++++++++++ .../offchain/constants.lua | 21 ++++ .../offchain/cryptography/hash.lua | 2 +- .../offchain/cryptography/merkle_builder.lua | 2 + .../offchain/entrypoint.lua | 50 ++++++++ .../offchain/program/.gitignore | 2 + .../offchain/program/README.md | 29 +++++ .../offchain/program/bins/bootstrap.bin | Bin 0 -> 16 bytes .../offchain/program/bins/rv64ui-p-addi.bin | Bin 0 -> 1148 bytes .../offchain/program/gen_machine_linux.sh | 5 + .../offchain/program/gen_machine_simple.sh | 9 ++ 13 files changed, 326 insertions(+), 31 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/computation/commitment.lua create mode 100644 onchain/permissionless-arbitration/offchain/computation/machine.lua create mode 100644 onchain/permissionless-arbitration/offchain/constants.lua create mode 100755 onchain/permissionless-arbitration/offchain/entrypoint.lua create mode 100644 onchain/permissionless-arbitration/offchain/program/.gitignore create mode 100644 onchain/permissionless-arbitration/offchain/program/README.md create mode 100755 onchain/permissionless-arbitration/offchain/program/bins/bootstrap.bin create mode 100755 onchain/permissionless-arbitration/offchain/program/bins/rv64ui-p-addi.bin create mode 100755 onchain/permissionless-arbitration/offchain/program/gen_machine_linux.sh create mode 100755 onchain/permissionless-arbitration/offchain/program/gen_machine_simple.sh diff --git a/onchain/permissionless-arbitration/offchain/README.md b/onchain/permissionless-arbitration/offchain/README.md index 4d49bcb32..18927788c 100644 --- a/onchain/permissionless-arbitration/offchain/README.md +++ b/onchain/permissionless-arbitration/offchain/README.md @@ -3,34 +3,5 @@ ## Run example ``` -docker build -t nxn_playground:latest . && \ -docker run --platform linux/amd64 --rm nxn_playground:latest -``` - -## Generate programs - -``` -cd program -``` - -``` -docker run --platform linux/amd64 -it --rm -h playground \ - -e USER=$(id -u -n) \ - -e GROUP=$(id -g -n) \ - -e UID=$(id -u) \ - -e GID=$(id -g) \ - -v (pwd):/home/$(id -u -n) \ - -w /home/$(id -u -n) \ - diegonehab/playground:develop /bin/bash -c "./gen_machine_linux.sh" -``` - -``` -docker run --platform linux/amd64 -it --rm -h playground \ - -e USER=$(id -u -n) \ - -e GROUP=$(id -g -n) \ - -e UID=$(id -u) \ - -e GID=$(id -g) \ - -v (pwd):/home/$(id -u -n) \ - -w /home/$(id -u -n) \ - diegonehab/playground:develop /bin/bash -c "./gen_machine_simple.sh" +docker build -t nxn_playground:latest . && docker run --platform linux/amd64 --rm nxn_playground:latest ``` diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua new file mode 100644 index 000000000..494f09547 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -0,0 +1,108 @@ +local MerkleBuilder = require "cryptography.merkle_builder" +local Machine = require "computation.machine" + +local arithmetic = require "utils.arithmetic" +local consts = require "constants" + +local ulte = arithmetic.ulte + +local function build_small_machine_commitment(base_cycle, log2_stride_count, machine) + machine:advance(base_cycle) + local initial_state = machine:result().state + + local outer_builder = MerkleBuilder:new() + local big_instructions = 1 << (log2_stride_count - consts.a) + local big_machine_halted = false + + local big_instruction = 0 + while math.ult(big_instruction, big_instructions) do + local inner_builder = MerkleBuilder:new() + + -- This loop runs from `1` to `2^a - 1`, for a total of `2^a - 1` times + local ucycle = 1 + local small_instructions = arithmetic.max_int(consts.a) + while ulte(ucycle, small_instructions) do + machine:uadvance(ucycle) + local state = machine:result() + + if not state.uhalted then + inner_builder:add(state.state) + else + -- add this loop plus all remainings + inner_builder:add(state.state, small_instructions - ucycle + 1) + break + end + + ucycle = ucycle + 1 + end + + -- At this point, we've added `2^a - 1` hashes to the inner merkle builder. + -- Now we do the last state transition (ureset), and add the last state, + -- closing in a power-of-two number of leaves (`2^a` leaves). + machine:ureset() + local state = machine:result() + inner_builder:add(state.state) + + if not big_machine_halted then + outer_builder:add(inner_builder:build()) + big_machine_halted = state.halted + else + -- add this loop plus all remainings + outer_builder:add(inner_builder:build(), big_instructions - big_instruction + 1) + break + end + + big_instruction = big_instruction + 1 + end + + return initial_state, outer_builder:build() +end + + + +local function build_big_machine_commitment(base_cycle, log2_stride, log2_stride_count, machine) + machine:advance(base_cycle + 0) + local initial_state = machine:result().state + + local builder = MerkleBuilder:new() + local strides = (1 << log2_stride_count) - 1 + + local stride = 0 + while ulte(stride, strides) do + local cycle = ((stride + 1) << (log2_stride - consts.a)) + machine:advance(base_cycle + cycle) + + local state = machine:result() + if not state.halted then + builder:add(state.state) + else + -- add this loop plus all remainings + builder:add(state.state, strides - stride + 1) + break + end + + stride = stride + 1 + end + + return initial_state, builder:build() +end + +local function build_commitment(base_cycle, log2_stride, log2_stride_count, machine_path) + local machine = Machine:new_from_path(machine_path) + + if log2_stride >= consts.a then + assert(log2_stride - consts.a + log2_stride_count <= 63) + return build_big_machine_commitment(base_cycle, log2_stride, log2_stride_count, machine) + else + assert(log2_stride == 0) + return build_small_machine_commitment(base_cycle, log2_stride_count, machine) + end +end + +local path = "program/simple-program" + +local initial, tree = build_commitment(0, 0, 64, path) +-- local initial, tree = build_commitment(0, 64, 63, path) +print(initial, tree.root_hash) + +return build_commitment diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua new file mode 100644 index 000000000..3fb62110a --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -0,0 +1,98 @@ +local Hash = require "cryptography.hash" +local arithmetic = require "utils.arithmetic" +local cartesi = require "cartesi" + +local ComputationResult = {} +ComputationResult.__index = ComputationResult + +function ComputationResult:new(state, halted, uhalted) + local r = { + state = state, + halted = halted, + uhalted = uhalted + } + setmetatable(r, self) + return r +end + +function ComputationResult:from_current_machine_state(machine) + local hash = Hash:from_digest_bin(machine:get_root_hash()) + return ComputationResult:new( + hash, + machine:read_iflags_H(), + machine:read_uarch_halt_flag() + ) +end + +ComputationResult.__tostring = function(x) + return string.format( + "{state = %s, halted = %s, uhalted = %s}", + x.state, + x.halted, + x.uhalted + ) +end + + +-- +--- +-- + +local Machine = {} +Machine.__index = Machine + +function Machine:new_from_path(path) + local machine = cartesi.machine(path) + local start_cycle = machine:read_mcycle() + + -- Machine can never be advanced on the micro arch. + -- Validators must verify this first + assert(machine:read_uarch_cycle() == 0) + + local b = { + machine = machine, + cycle = 0, + ucycle = 0, + start_cycle = start_cycle, + } + + setmetatable(b, self) + return b +end + +function Machine:result() + return ComputationResult:from_current_machine_state(self.machine) +end + +local function add_and_clamp(x, ...) + for _,v in ipairs {...} do + if arithmetic.ulte(x, x + v) then + x = x + v + else + return -1 + end + end + + return x +end + +function Machine:advance(cycle, ...) + cycle = add_and_clamp(cycle, ...) + assert(self.cycle <= cycle) + self.machine:run(add_and_clamp(self.start_cycle, cycle)) + self.cycle = cycle +end + +function Machine:uadvance(ucycle) + assert(arithmetic.ulte(self.ucycle, ucycle), string.format("%u, %u", self.ucycle, ucycle)) + self.machine:run_uarch(ucycle) + self.ucycle = ucycle +end + +function Machine:ureset() + self.machine:reset_uarch_state() + self.cycle = self.cycle + 1 + self.ucycle = 0 +end + +return Machine diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua new file mode 100644 index 000000000..b61442c04 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -0,0 +1,21 @@ +local constants = { + levels = 4, + max_cycle = 63, + log2step = {24, 14, 7, 0}, + heights = {39, 10, 7, 7}, + a = 64, b = 63, +} + +--[[ +a = 2 +b = 2 + +states = 2^b + 1 + +x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) + +0 1 2 3 0 1 2 3 0 0 + +--]] + +return constants diff --git a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua index fff87c355..3bba7614e 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua @@ -50,7 +50,7 @@ function Hash:join(other_hash) end function Hash:children() - local left, right= self.left, self.right + local left, right = self.left, self.right if left and right then return true, left, right else diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua index 09c360c58..152c4d7b3 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua @@ -48,6 +48,8 @@ function Slice:find_cell_containing(elem) while math.ult(l, r) do local m = semi_sum(l, r) + + -- `-1` on both sides changes semantics on underflow... zero means 2^64. if math.ult(self:get(m).accumulated_count - 1, elem - 1) then l = m + 1 else diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua new file mode 100755 index 000000000..f7da0a2a2 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -0,0 +1,50 @@ +#!/usr/bin/lua +package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" + +print "Hello, world!" +os.execute "cd program && ./gen_machine_simple.sh" + +-- os.execute "jsonrpc-remote-cartesi-machine --server-address=localhost:8080 &" +-- os.execute "sleep 2" + +-- require "cryptography.merkle_builder" +require "computation.commitment" +-- require "computation.machine_test" + + +-- local utils = require "utils" +-- local cartesi = {} +-- cartesi.rpc = require"cartesi.grpc" + +-- local remote = cartesi.rpc.stub("localhost:8080", "localhost:8081") +-- local v = assert(remote.get_version()) +-- print(string.format("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch)) + +-- local machine = remote.machine("program/simple-program") +-- print("cycles", machine:read_mcycle(), machine:read_uarch_cycle()) +-- machine:snapshot() +-- machine:snapshot() + +-- print(utils.hex_from_bin(machine:get_root_hash())) +-- machine:run(1000) +-- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) +-- machine:rollback() + +-- print(utils.hex_from_bin(machine:get_root_hash())) +-- machine:run(1000) +-- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) +-- machine:rollback() + +-- print(utils.hex_from_bin(machine:get_root_hash())) +-- machine:run(1000) +-- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) + + + + +-- machine:read_mcycle() + + + +print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/program/.gitignore b/onchain/permissionless-arbitration/offchain/program/.gitignore new file mode 100644 index 000000000..6e9e42527 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/program/.gitignore @@ -0,0 +1,2 @@ +simple-linux-program/ +simple-program/ diff --git a/onchain/permissionless-arbitration/offchain/program/README.md b/onchain/permissionless-arbitration/offchain/program/README.md new file mode 100644 index 000000000..4f74431a7 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/program/README.md @@ -0,0 +1,29 @@ +# RISC-V programs + +## Generate programs + +``` +cd program +``` + +``` +docker run --platform linux/amd64 -it --rm -h playground \ + -e USER=$(id -u -n) \ + -e GROUP=$(id -g -n) \ + -e UID=$(id -u) \ + -e GID=$(id -g) \ + -v (pwd):/home/$(id -u -n) \ + -w /home/$(id -u -n) \ + diegonehab/playground:develop /bin/bash -c "./gen_machine_linux.sh" +``` + +``` +docker run --platform linux/amd64 -it --rm -h playground \ + -e USER=$(id -u -n) \ + -e GROUP=$(id -g -n) \ + -e UID=$(id -u) \ + -e GID=$(id -g) \ + -v (pwd):/home/$(id -u -n) \ + -w /home/$(id -u -n) \ + diegonehab/playground:develop /bin/bash -c "./gen_machine_simple.sh" +``` diff --git a/onchain/permissionless-arbitration/offchain/program/bins/bootstrap.bin b/onchain/permissionless-arbitration/offchain/program/bins/bootstrap.bin new file mode 100755 index 0000000000000000000000000000000000000000..82b19be5ee0d56ce6a94812526a8d06564aa1f6c GIT binary patch literal 16 XcmbQuB)~9v(kI4ZRfmu14NMFGFI5G2 literal 0 HcmV?d00001 diff --git a/onchain/permissionless-arbitration/offchain/program/bins/rv64ui-p-addi.bin b/onchain/permissionless-arbitration/offchain/program/bins/rv64ui-p-addi.bin new file mode 100755 index 0000000000000000000000000000000000000000..104034ad5eed75e33dde0c88dda498294b792175 GIT binary patch literal 1148 zcmbVLO=}cE5UuXnHEWE{xV^5+62cHa@a7;n>A}Tc5TZv9fj)T5LLcTh%?4TU;v^+_ zSkU~0#mipv6Y>)xxyxwqCXTOWcW@L>4m^skepU6ls^)-v%RP*DIP_#YWx!!hwk38> zmde0{gmXx*C?w(87a<+ci1_o-^V@If^vZRL<|@W3zz^FFfCZXv|#~r z1r{)Cuz=~n0_G|#VAf#)a}5?S8?b=64hxtYuz+Y|z}*{twNabiOWlc9F5gSsE)ACF zXIjoP9T6Yz1w8pyx?DeDU4W+_yWLT;;;rIh#3qTnW9grKk$BUN$lUs_?8`PfQG~3J zuXEOxH#xjr-fQ1z0ykQGBAQK(BbRTBjz<3Oj%alhBY#X)&!&h)k8C%6z$$?cb*9K2 z#AFBQeO4p*68OgOosiv1@3PvhV?AQM?TK%n7DV14-$H!Dbeq+<_W4;*l+2fk4+C#E z)0mZCznRXln`_w9F6I`~MSt&S#fw~o88mn&WMr?K+@5g9 z$`0KIX4By2OYYzNuJHGj?Yec$t-*(ey8fW9pLQ_E-yF#`g@&3}=|t_#w1wIJ$*Z`~ zP;)DtsM}99W{rN3MnARvFvdTc;~Z(@ Date: Tue, 18 Jul 2023 20:43:26 -0300 Subject: [PATCH 05/25] fix(arbitration): fix hash implementation Change hashing from being done on hex string represantations to binary data. --- .../offchain/computation/commitment.lua | 11 ++++ .../offchain/computation/machine.lua | 2 +- .../offchain/constants.lua | 12 ----- .../offchain/cryptography/hash.lua | 53 ++++++++++--------- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua index 494f09547..f445d1527 100644 --- a/onchain/permissionless-arbitration/offchain/computation/commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -6,6 +6,17 @@ local consts = require "constants" local ulte = arithmetic.ulte +--[[ +a = 2 +b = 2 + +states = 2^b + 1 + +x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) +0 1 2 3 0 1 2 3 0 1 + +--]] + local function build_small_machine_commitment(base_cycle, log2_stride_count, machine) machine:advance(base_cycle) local initial_state = machine:result().state diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua index 3fb62110a..100da4923 100644 --- a/onchain/permissionless-arbitration/offchain/computation/machine.lua +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -16,7 +16,7 @@ function ComputationResult:new(state, halted, uhalted) end function ComputationResult:from_current_machine_state(machine) - local hash = Hash:from_digest_bin(machine:get_root_hash()) + local hash = Hash:from_digest(machine:get_root_hash()) return ComputationResult:new( hash, machine:read_iflags_H(), diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua index b61442c04..65f4b2970 100644 --- a/onchain/permissionless-arbitration/offchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -6,16 +6,4 @@ local constants = { a = 64, b = 63, } ---[[ -a = 2 -b = 2 - -states = 2^b + 1 - -x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) - -0 1 2 3 0 1 2 3 0 0 - ---]] - return constants diff --git a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua index 3bba7614e..14663ea52 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua @@ -1,14 +1,18 @@ -local keccak_bin = require "cartesi".keccak +local keccak = require "cartesi".keccak local function hex_from_bin(bin) assert(bin:len() == 32) - return "0x" .. string.gsub(bin, ".", function(c) - return string.format("%02x", string.byte(c)) - end) + return "0x" .. (bin:gsub('.', function (c) + return string.format('%02x', string.byte(c)) + end)) end -local function keccak(...) - return hex_from_bin(keccak_bin(...)) +local function bin_from_hex(hex) + assert(hex:len() == 66, string.format("%s %d", hex, hex:len())) + local h = assert(hex:match("0x(%x+)"), hex) + return (h:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) end local internalized_hahes = {} @@ -17,35 +21,37 @@ local iterateds = {} local Hash = {} Hash.__index = Hash -function Hash:from_digest(digest_hex) - assert(type(digest_hex) == "string", digest_hex:len() == 66) +function Hash:from_digest(digest) + assert(type(digest) == "string", digest:len() == 32) - local x = internalized_hahes[digest_hex] + local x = internalized_hahes[digest] if x then return x end - local h = {digest_hex = digest_hex} + local h = {digest = digest} iterateds[h] = {h} setmetatable(h, self) - internalized_hahes[digest_hex] = h + internalized_hahes[digest] = h return h end -function Hash:from_digest_bin(digest_bin) - local digest_hex = hex_from_bin(digest_bin) - return self:from_digest(digest_hex) +function Hash:from_digest_hex(digest_hex) + assert(type(digest_hex) == "string", digest_hex:len() == 66) + local digest = bin_from_hex(digest_hex) + return self:from_digest(digest) end function Hash:from_data(data) - local digest_hex = keccak(data) - return self:from_digest(digest_hex) + local digest = keccak(data) + return self:from_digest(digest) end function Hash:join(other_hash) - assert(getmetatable(other_hash) == Hash) - local digest_hex = keccak(self.digest_hex, other_hash.digest_hex) - local ret = Hash:from_digest(digest_hex) - ret.left = self.digest_hex - ret.right = other_hash.digest_hex + Hash:is_of_type_hash(other_hash) + + local digest = keccak(self.digest, other_hash.digest) + local ret = Hash:from_digest(digest) + ret.left = self.digest + ret.right = other_hash.digest return ret end @@ -77,11 +83,11 @@ function Hash:iterated_merkle(level) end Hash.__tostring = function (x) - return x.digest_hex + return hex_from_bin(x.digest) end local zero_bytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000" -local zero_hash = Hash:from_digest(zero_bytes32) +local zero_hash = Hash:from_digest_hex(zero_bytes32) Hash.zero = zero_hash @@ -89,7 +95,6 @@ function Hash:is_zero() return self == zero_hash end - function Hash:is_of_type_hash(x) return getmetatable(x) == self end From 0a10b01fe80c22b37e4f229b096279ac96cd3510 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 20 Jul 2023 10:02:39 -0300 Subject: [PATCH 06/25] feat(arbitration): add old blockchain and player scripts --- .../offchain/blockchain/client.lua | 524 ++++++++++++++++++ .../offchain/blockchain/node.lua | 168 ++++++ .../offchain/player.lua | 261 +++++++++ 3 files changed, 953 insertions(+) create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/client.lua create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/node.lua create mode 100644 onchain/permissionless-arbitration/offchain/player.lua diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua new file mode 100644 index 000000000..8b4edba9b --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -0,0 +1,524 @@ +local Hash = require "cryptography.hash" +local utils = require "utils" + +local function parse_topics(json) + local _, _, topics = json:find( + [==["topics":%[([^%]]*)%]]==] + ) + + local t = {} + for k,_ in string.gmatch(topics, [["(0x%x+)"]]) do + table.insert(t, k) + end + + return t +end + +local function parse_data(json, sig) + local _, _, data = json:find( + [==["data":"(0x%x+)"]==] + ) + + local decoded_data = utils.decode_event_data(sig, data) + return decoded_data +end + +local function parse_meta(json) + local _, _, block_hash = json:find( + [==["blockHash":"(0x%x+)"]==] + ) + + local _, _, block_number = json:find( + [==["blockNumber":"(0x%x+)"]==] + ) + + local _, _, log_index = json:find( + [==["logIndex":"(0x%x+)"]==] + ) + + local t = { + block_hash = block_hash, + block_number = tonumber(block_number), + log_index = tonumber(log_index), + } + + return t +end + + +local function parse_logs(logs, data_sig) + local ret = {} + for k,_ in string.gmatch(logs, [[{[^}]*}]]) do + local emited_topics = parse_topics(k) + local decoded_data = parse_data(k, data_sig) + local meta = parse_meta(k) + table.insert(ret, {emited_topics = emited_topics, decoded_data = decoded_data, meta = meta}) + end + + return ret +end + +local function join_tables(...) + local function join(ret, t, ...) + if not t then return ret end + + for k,v in ipairs(t) do + ret[k] = v + end + + return join(ret, ...) + end + + local ret = join({}, ...) + return ret +end + +local function sort_and_dedup(t) + table.sort(t, function(a, b) + local m1, m2 = a.meta, b.meta + + if m1.block_number < m2.block_number then + return true + elseif m1.block_number > m2.block_number then + return false + else + if m1.log_index <= m2.log_index then + return true + else + return false + end + end + end) + + local ret = {} + for k,v in ipairs(t) do + local v2 = t[k+1] + if not v2 then + table.insert(ret, v) + else + local m1, m2 = v.meta, v2.meta + if not (m1.block_number == m2.block_number and m1.log_index == m2.log_index) then + table.insert(ret, v) + end + end + end + + return ret +end + +local function quote_args(args, not_quote) + local quoted_args = {} + for _,v in ipairs(args) do + if type(v) == "table" then + if v._tag == "tuple" then + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'(" .. ca .. ")'" + table.insert(quoted_args, sb) + else + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'[" .. ca .. "]'" + table.insert(quoted_args, sb) + end + elseif not_quote then + table.insert(quoted_args, v) + else + table.insert(quoted_args, '"'..v..'"') + end + end + + return quoted_args +end + + +local Client = {} +Client.__index = Client + +function Client:new(blockchain) + local client = { + endpoint = blockchain.endpoint, + account = blockchain:new_account(), + blockchain = blockchain, + } + + setmetatable(client, self) + return client + +end + +local cast_logs_template = [==[ +cast rpc -r "%s" eth_getLogs \ + '[{"fromBlock": "earliest", "toBlock": "latest", "address": "%s", "topics": [%s]}]' -w 2>&1 +]==] + +function Client:_read_logs(tournament_address, sig, topics, data_sig) + topics = topics or {false, false, false} + local encoded_sig = utils.encode_sig(sig) + table.insert(topics, 1, encoded_sig) + assert(#topics == 4, "topics doesn't have four elements") + + local topics_strs = {} + for _,v in ipairs(topics) do + local s + if v then + s = '"'..v..'"' + else + s = "null" + end + table.insert(topics_strs, s) + end + local topic_str = table.concat(topics_strs, ", ") + + local cmd = string.format( + cast_logs_template, + self.endpoint, + tournament_address, + topic_str + ) + + local handle = io.popen(cmd) + assert(handle) + local logs = handle:read "*a" + handle:close() + + if logs:find "Error" then + error(string.format("Read logs `%s` failed:\n%s", sig, logs)) + end + + local ret = parse_logs(logs, data_sig) + + self.blockchain:read_to("eth_getLogs") + return ret +end + +local cast_call_template = [==[ +cast call --rpc-url "%s" "%s" "%s" %s 2>&1 +]==] + +function Client:_call(address, sig, args) + local quoted_args = {} + for _,v in ipairs(args) do + table.insert(quoted_args, '"'..v..'"') + end + local args_str = table.concat(quoted_args, " ") + + local cmd = string.format( + cast_call_template, + self.endpoint, + address, + sig, + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local ret = {} + local str = handle:read() + while str do + if str:find "Error" or str:find "error" then + local err_str = handle:read "*a" + handle:close() + error(string.format("Call `%s` failed:\n%s%s", sig, str, err_str)) + end + + table.insert(ret, str) + str = handle:read() + end + handle:close() + + self.blockchain:read_to("eth_call") + return ret +end + +function Client:read_match_created(tournament_address, commitment_hash) + local sig = "matchCreated(bytes32,bytes32,bytes32)" + local data_sig = "(bytes32)" + + local logs1 = self:_read_logs(tournament_address, sig, {commitment_hash, false, false}, data_sig) + local logs2 = self:_read_logs(tournament_address, sig, {false, commitment_hash, false}, data_sig) + + local logs = sort_and_dedup(join_tables(logs1, logs2)) + + local ret = {} + for k,v in ipairs(logs) do + local log = {} + log.tournament_address = tournament_address + log.meta = v.meta + + log.commitment_one = Hash:from_digest(v.emited_topics[2]) + log.commitment_two = Hash:from_digest(v.emited_topics[3]) + log.left_hash = Hash:from_digest(v.decoded_data[1]) + log.match_id_hash = log.commitment_one:join(log.commitment_two) + + ret[k] = log + end + + return ret +end + +function Client:read_commitment(tournament_address, commitment_hash) + local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" + + local call_ret = self:_call(tournament_address, sig, {commitment_hash}) + assert(#call_ret == 2) + + local allowance, last_resume = call_ret[1]:match "%((%d+),(%d+)%)" + assert(allowance) + assert(last_resume) + local clock = { + allowance = tonumber(allowance), + last_resume = tonumber(last_resume) + } + + local ret = { + clock = clock, + final_state = Hash:from_digest(call_ret[2]) + } + + return ret +end + +function Client:read_tournament_created(tournament_address, match_id_hash) + local sig = "newInnerTournament(bytes32,address)" + local data_sig = "(address)" + + local logs = self:_read_logs(tournament_address, sig, {match_id_hash, false, false}, data_sig) + assert(#logs <= 1) + + if #logs == 0 then return false end + local log = logs[1] + + local ret = { + parent_match = Hash:from_digest(match_id_hash), + new_tournament = log.decoded_data[1], + } + + return ret +end + +function Client:match(address, match_id_hash) + local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint64,uint64,uint64)" + local ret = self:_call(address, sig, {match_id_hash}) + ret[1] = Hash:from_digest(ret[1]) + ret[2] = Hash:from_digest(ret[2]) + ret[3] = Hash:from_digest(ret[3]) + + return ret +end + +function Client:tournament_winner(address) + local sig = "tournamentWinner()(bytes32)" + local ret = self:_call(address, sig, {}) + + return Hash:from_digest(ret[1]) +end + +function Client:root_tournament_winner(address) + local sig = "rootTournamentFinalState()(bool,bytes32)" + local ret = self:_call(address, sig, {}) + ret[2] = Hash:from_digest(ret[2]) + + return ret +end + + +function Client:maximum_delay(address) + local sig = "maximumEnforceableDelay()(uint64)" + local ret = self:_call(address, sig, {}) + + return ret +end + + +local cast_send_template = [[ +cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 +]] + +function Client:_send_tx(tournament_address, sig, args) + local quoted_args = quote_args(args) + local args_str = table.concat(quoted_args, " ") + + local cmd = string.format( + cast_send_template, + self.account.pk, + self.endpoint, + tournament_address, + sig, + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local ret = handle:read "*a" + if ret:find "Error" then + handle:close() + error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) + end + handle:close() + self.blockchain:read_to("eth_sendRawTransaction") +end + +function Client:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) + local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] + self:_send_tx(tournament_address, sig, {final_state, proof, left_child, right_child}) +end + +function Client:tx_advance_match( + tournament_address, commitment_one, commitment_two, left, right, new_left, new_right +) + local sig = [[advanceMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32)]] + self:_send_tx( + tournament_address, + sig, + {{commitment_one, commitment_two, _tag = "tuple"}, left, right, new_left, new_right} + ) +end + + +function Client:tx_seal_inner_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + self:_send_tx( + tournament_address, + sig, + {{commitment_one, commitment_two, _tag = "tuple"}, left, right, initial_hash, proof} + ) +end + +function Client:tx_win_inner_match(tournament_address, child_tournament_address, left, right) + local sig = + [[winInnerMatch(address,bytes32,bytes32)]] + self:_send_tx( + tournament_address, + sig, + {child_tournament_address, left, right} + ) +end + +function Client:tx_seal_leaf_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + self:_send_tx( + tournament_address, + sig, + {{commitment_one, commitment_two, _tag = "tuple"}, left, right, initial_hash, proof} + ) +end + +function Client:tx_win_leaf_match( + tournament_address, commitment_one, commitment_two, left, right +) + local sig = + [[winLeafMatch((bytes32,bytes32),bytes32,bytes32)]] + self:_send_tx( + tournament_address, + sig, + {{commitment_one, commitment_two, _tag = "tuple"}, left, right} + ) +end + + +return Client + + +--[[ +local blockchain = require "blockchain":new() +local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" +local contract = blockchain:deploy_contract(initial_hash) + +local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) +local p1_l1 = utils.keccak "0" +local p1_l2 = utils.keccak "1" +local p1_cmt = utils.join_hashes(utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) +client1:tx_join_tournament(contract, p1_l2, {p1_l1}, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) +local commitment1 = client1:read_commitment(contract, p1_cmt) +print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) + +local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) +local p2_l1 = utils.keccak "0" +local p2_l2 = utils.keccak "2" +local p2_cmt = utils.join_hashes(utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) +client2:tx_join_tournament(contract, p2_l2, {p2_l1}, utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) +local commitment2 = client1:read_commitment(contract, p2_cmt) +print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) + +local a = client1:read_match_created(contract, false, false) +for k,log in ipairs(a) do + print("LOG ", k) + print("tournament_address", log.tournament_address) + print("commitment_one", log.commitment_one) + print("commitment_two", log.commitment_two) + print("match_id_hash", log.match_id_hash) + print("left_hash", log.left_hash) + print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) + print("END ", k) + print("") + + local x = client1:match(contract, log.match_id_hash) + for _,v in ipairs(x) do print("A", v) end +end + +client1:tx_advance_match(contract, p1_cmt, p2_cmt, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2), p1_l1, p1_l2) + +client2:tx_seal_inner_match(contract, p1_cmt, p2_cmt, p2_l1, p2_l2, p2_l1, {}) +client1:read_tournament_created(contract, utils.join_hashes(p1_cmt, p2_cmt)) +--]] + +-- local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) +-- local p1_l1 = utils.keccak "0" +-- local p1_l2 = utils.keccak "1" +-- local p1_cmt = utils.join_hashes(p1_l1, p1_l2) +-- client1:tx_join_tournament(contract, p1_l2, {p1_l1}, p1_l1, p1_l2) +-- local commitment1 = client1:read_commitment(contract, p1_cmt) +-- print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) + +-- local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) +-- local p2_l1 = utils.keccak "0" +-- local p2_l2 = utils.keccak "2" +-- local p2_cmt = utils.join_hashes(p2_l1, p2_l2) +-- client2:tx_join_tournament(contract, p2_l2, {p2_l1}, p2_l1, p2_l2) +-- local commitment2 = client1:read_commitment(contract, p2_cmt) +-- print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) + +-- local a = client1:read_match_created(contract, false, false) +-- for k,log in ipairs(a) do +-- print("LOG ", k) +-- print("tournament_address", log.tournament_address) +-- print("commitment_one", log.commitment_one) +-- print("commitment_two", log.commitment_two) +-- print("match_id_hash", log.match_id_hash) +-- print("left_hash", log.left_hash) +-- print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) +-- print("END ", k) +-- print("") + +-- local x = client1:match(contract, log.match_id_hash) +-- for _,v in ipairs(x) do print("A", v) end +-- end + + + +-- client.joinRootTournament(commitmentId, leftChild, rightChild) +-- client.winByTimeout(id) +-- client.advanceMatch(id, leftChild, rightChild, leftChildChild, rightChildChild) +-- client.sealMatch() // seals match, creates nested tournament, updates current commitment +-- client.enterNestedTournament(parentMatch, parentCommitmentIdHash, childCommitment) +-- client.proveLeafMatch() + + +-- local LEVEL_LOG2_STEP_SIZES = { 24, 14, 7, 0 } +-- local LOG2_MAX_MCYCLE = 63 +-- local last_log2_num_steps = LOG2_MAX_MCYCLE + +-- for i, log2_step_size in ipairs(LEVEL_LOG2_STEP_SIZES) do +-- local log2_num_steps = last_log2_num_steps - log2_step_size +-- print(i, log2_num_steps) +-- last_log2_num_steps = log2_step_size +-- end diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua new file mode 100644 index 000000000..d1ad2af50 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -0,0 +1,168 @@ +local default_account_number = 10 + +local function stop_blockchain(handle, pid) + print(string.format("Stopping blockchain with pid %d...", pid)) + os.execute(string.format("kill -15 %i", pid)) + handle:close() + print "Blockchain stopped" +end + +local function start_blockchain(account_num) + account_num = account_num or default_account_number + print(string.format("Starting blockchain with %d accounts...", account_num)) + + local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d"]], account_num) + + local reader = io.popen(cmd) + assert(reader, "`popen` returned nil reader") + + local pid = tonumber(reader:read()) + + local handle = {reader = reader, pid = pid} + setmetatable(handle, {__gc = function(t) + stop_blockchain(t.reader, t.pid) + end}) + + print(string.format("Blockchain running with pid %d", pid)) + return handle +end + +local function capture_blockchain_data(reader, account_num) + account_num = account_num or default_account_number + local str + + local addresses = {} + repeat + str = reader:read(); + local _, _, address = str:find [[%(%d+%) ("0x%x+")]] + if address then + table.insert(addresses, address) + end + until str:find("Private Keys") + assert(#addresses == account_num) + + local pks = {} + repeat + str = reader:read(); + local _, _, pk = str:find("%(%d+%) (0x%x+)") + if pk then + table.insert(pks, pk) + end + until str:find("Wallet") + assert(#pks == account_num) + + local endpoint + repeat + str = reader:read(); + _, _, endpoint = str:find("Listening on ([%w%p]+)") + until endpoint + + return {address = addresses, pk = pks}, endpoint +end + + +local function deploy_contracts(endpoint, deployer, initial_hash) + + -- Deploy Inner Factory + print "Deploying inner factory..." + + local cmd_inner = string.format( + [[sh -c "forge create InnerTournamentFactory --rpc-url=%s --private-key=%s"]], + endpoint, deployer + ) + + local handle_inner = io.popen(cmd_inner) + assert(handle_inner, "`popen` returned nil handle") + + local _, _, inner_factory_address = handle_inner:read("*a"):find("Deployed to: (0x%x+)") + assert(inner_factory_address, "deployment failed, factory_address is nil") + print("Inner factory deployed at", inner_factory_address) + handle_inner:close() + + -- + -- Deploy Root Factory + print "Deploying root factory..." + + local cmd_root = string.format( + [[sh -c "forge create RootTournamentFactory --rpc-url=%s --private-key=%s --constructor-args %s"]], + endpoint, deployer, inner_factory_address + ) + + local handle_root = io.popen(cmd_root) + assert(handle_root, "`popen` returned nil handle") + + local _, _, root_factory_address = handle_root:read("*a"):find("Deployed to: (0x%x+)") + assert(root_factory_address, "deployment failed, factory_address is nil") + print("Root factory deployed at", root_factory_address) + handle_root:close() + + + -- + -- Instantiate Root Tournament + print "Instantiate root contract..." + + local cmd2 = string.format( + [[cast send --private-key "%s" --rpc-url "%s" "%s" "instantiateTopOfMultiple(bytes32)" "%s"]], + deployer, endpoint, root_factory_address, initial_hash + ) + + local handle2 = io.popen(cmd2) + assert(handle2, "`popen` returned nil handle") + + local _, _, a = handle2:read("*a"):find [["data":"0x000000000000000000000000(%x+)"]] + local address = "0x" .. a + assert(address, "deployment failed, address is nil") + print("Contract deployed at", address) + handle2:close() + + return address +end + +local Blockchain = {} +Blockchain.__index = Blockchain + +function Blockchain:new(account_num) + local blockchain = {} + + local handle = start_blockchain(account_num) + local accounts, endpoint = capture_blockchain_data(handle.reader, account_num) + + blockchain._handle = handle + blockchain._accounts = accounts + blockchain._current_account = 1 + blockchain.endpoint = "http://"..endpoint + + setmetatable(blockchain, self) + return blockchain +end + +function Blockchain:new_account() + local current_account = self._current_account + self._current_account = current_account + 1 + local accounts = self._accounts + assert(current_account <= #accounts.address, "no more accounts") + + local account = { + address = accounts.address[current_account], + pk = accounts.pk[current_account] + } + + return account +end + +function Blockchain:deploy_contract(initial_hash, deployer) + assert(initial_hash) + deployer = deployer or self:new_account() + local address = deploy_contracts(self.endpoint, deployer.pk, initial_hash) + return address, deployer +end + +function Blockchain:read_to(p) + repeat until self._handle.reader:read():find(p) +end + +-- local bc = Blockchain:new(100) +-- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" +-- bc:deploy_contract(initial_hash) + +return Blockchain diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua new file mode 100644 index 000000000..12abd061c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player.lua @@ -0,0 +1,261 @@ +local constants = require "constants" + +local Player = {} +Player.__index = Player + +function Player:new(root_tournament_address, client, machine) + local player = { + root_tournament = { + address = root_tournament_address, + level = constants.levels, + parent = false, + }, + client = client, + machine = machine, + commitments = {}, + called_win = {} + } + + setmetatable(player, self) + return player +end + +function Player:react() + if self.has_lost then return end + return self:_react_tournament(self.root_tournament) +end + +function Player:_react_tournament(tournament) + local commitment = self.commitments[tournament.address] + if not commitment then + commitment = self.machine:commitment( + constants.log2step[tournament.level], + constants.heights[constants.levels - tournament.level + 1], + false, -- TODO + false -- TODO + ) + self.commitments[tournament.address] = commitment + end + + if not tournament.parent then + local winner_final_state = self.client:root_tournament_winner(tournament.address) + if winner_final_state[1] == "true" then + print "TOURNAMENT FINISHED, HURRAYYY" + print("Final state: " .. winner_final_state[2]) + return true + end + else + local tournament_winner = self.client:tournament_winner(tournament.address) + if not tournament_winner:is_zero() then + local old_commitment = self.commitments[tournament.parent.address] + if tournament_winner ~= old_commitment.root then + print "player lost tournament" + self.has_lost = true + return + end + + if self.called_win[tournament.address] then + print "player already called winInnerMatch" + return + else + self.called_win[tournament.address] = true + end + + print(string.format( + "win tournament %s of level %d for commitment %s", + tournament.address, + tournament.level, + commitment.root + )) + local _, left, right = old_commitment:children(old_commitment.root) + self.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) + return + end + end + + local latest_match = self:_latest_match(tournament, commitment) + + if not latest_match then + self:_join_tournament_if_needed(tournament, commitment) + else + self:_react_match(latest_match, commitment) + end +end + +function Player:_react_match(match, commitment) + -- TODO call timeout if needed + + -- print("HEIGHT", match.current_height) + if match.current_height == 0 then + -- match sealed + if match.tournament.level == 1 then + local f, left, right = commitment:children(commitment.root) + assert(f) + + local finished = + self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() + + if finished then + local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) + print("DELAY", delay - os.time()) + return + end + + print(string.format( + "win leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root + )) + self.client:tx_win_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right + ) + + else + local address = self.client:read_tournament_created( + match.tournament.address, + match.match_id_hash + ).new_tournament + + local new_tournament = {} + new_tournament.address = address + new_tournament.level = match.tournament.level - 1 + new_tournament.parent = match.tournament + + return self:_react_tournament(new_tournament) + end + + elseif match.current_height == 1 then + -- match to be sealed + local found, left, right = commitment:children(match.current_other_parent) + if not found then return end + + local initial_hash, proof + if match.running_leaf == 0 then + initial_hash, proof = self.machine.initial_hash, {} + else + initial_hash, proof = commitment:prove_leaf(match.running_leaf) + end + + if match.tournament.level == 1 then + print(string.format( + "seal leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root + )) + self.client:tx_seal_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + else + print(string.format( + "seal inner match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root + )) + self.client:tx_seal_inner_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + + local address = self.client:read_tournament_created( + match.tournament.address, + match.match_id_hash + ).new_tournament + + local new_tournament = {} + new_tournament.address = address + new_tournament.level = match.tournament.level - 1 + new_tournament.parent = match.tournament + + return self:_react_tournament(new_tournament) + end + else + -- match running + local found, left, right = commitment:children(match.current_other_parent.digest_hex) + if not found then return end + + local new_left, new_right + if left ~= match.current_left then + local f + f, new_left, new_right = commitment:children(left) + assert(f) + else + local f + f, new_left, new_right = commitment:children(right) + assert(f) + end + + print(string.format( + "advance match with current height %d in tournament %s of level %d for commitment %s", + match.current_height, + match.tournament.address, + match.tournament.level, + commitment.root + )) + self.client:tx_advance_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + new_left, + new_right + ) + end +end + + +function Player:_latest_match(tournament, commitment) + local matches = self.client:read_match_created(tournament.address, commitment.root) + local last_match = matches[#matches] + + if not last_match then return false end + + local m = self.client:match(tournament.address, last_match.match_id_hash) + if m[1]:is_zero() then return false end + last_match.current_other_parent = m[1] + last_match.current_left = m[2] + last_match.running_leaf = tonumber(m[4]) + last_match.height = tonumber(m[5]) + last_match.current_height = tonumber(m[6]) + last_match.tournament = tournament + + return last_match +end + +function Player:_join_tournament_if_needed(tournament, commitment) + local c = self.client:read_commitment(tournament.address, commitment.root) + + if c.clock.allowance == 0 then + local f, left, right = commitment:children(commitment.root) + assert(f) + local last, proof = commitment:last() + + print(string.format( + "join tournament %s of level %d with commitment %s", + tournament.address, + tournament.level, + commitment.root + )) + self.client:tx_join_tournament(tournament.address, last, proof, left, right) + end +end + +return Player From 515b45f222cf8511433f8589d8ec25b3f56f12ca Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 27 Jul 2023 15:06:22 +0300 Subject: [PATCH 07/25] feat(arbitration): refactor commitment generation --- .../offchain/computation/commitment.lua | 180 +++++++++++------- .../offchain/computation/machine.lua | 60 +++--- .../offchain/constants.lua | 14 +- .../offchain/utils/arithmetic.lua | 7 +- 4 files changed, 161 insertions(+), 100 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua index f445d1527..2f5781c18 100644 --- a/onchain/permissionless-arbitration/offchain/computation/commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -6,93 +6,74 @@ local consts = require "constants" local ulte = arithmetic.ulte ---[[ -a = 2 -b = 2 +local function run_uarch_span(machine) + assert(machine.ucycle == 0) + machine:increment_uarch() + local builder = MerkleBuilder:new() -states = 2^b + 1 + local i = 0 + repeat + builder:add(machine:state().root_hash) + machine:increment_uarch() + i = i + 1 + until machine:state().uhalted -x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) -0 1 2 3 0 1 2 3 0 1 + -- Add all remaining fixed-point states, filling the tree up to the last leaf. + builder:add(machine:state().root_hash, consts.uarch_span - i) ---]] + -- At this point, we've added `2^a - 1` hashes to the inner merkle builder. + -- Note that these states range from "meta" ucycle `1` to `2^a - 1`. -local function build_small_machine_commitment(base_cycle, log2_stride_count, machine) - machine:advance(base_cycle) - local initial_state = machine:result().state - - local outer_builder = MerkleBuilder:new() - local big_instructions = 1 << (log2_stride_count - consts.a) - local big_machine_halted = false - - local big_instruction = 0 - while math.ult(big_instruction, big_instructions) do - local inner_builder = MerkleBuilder:new() - - -- This loop runs from `1` to `2^a - 1`, for a total of `2^a - 1` times - local ucycle = 1 - local small_instructions = arithmetic.max_int(consts.a) - while ulte(ucycle, small_instructions) do - machine:uadvance(ucycle) - local state = machine:result() - - if not state.uhalted then - inner_builder:add(state.state) - else - -- add this loop plus all remainings - inner_builder:add(state.state, small_instructions - ucycle + 1) - break - end - - ucycle = ucycle + 1 - end + -- Now we do the last state transition (ureset), and add the last state, + -- closing in a power-of-two number of leaves (`2^a` leaves). + machine:ureset() + builder:add(machine:state().root_hash) + + return builder:build() +end - -- At this point, we've added `2^a - 1` hashes to the inner merkle builder. - -- Now we do the last state transition (ureset), and add the last state, - -- closing in a power-of-two number of leaves (`2^a` leaves). - machine:ureset() - local state = machine:result() - inner_builder:add(state.state) +local function build_small_machine_commitment(base_cycle, log2_stride_count, machine) + machine:run(base_cycle) + local initial_state = machine:state().root_hash - if not big_machine_halted then - outer_builder:add(inner_builder:build()) - big_machine_halted = state.halted - else - -- add this loop plus all remainings - outer_builder:add(inner_builder:build(), big_instructions - big_instruction + 1) + local builder = MerkleBuilder:new() + local instruction_count = arithmetic.max_uint(log2_stride_count - consts.log2_uarch_span) + local instruction = 0 + while ulte(instruction, instruction_count) do + builder:add(run_uarch_span(machine)) + instruction = instruction + 1 + + -- Optional optimization, just comment to remove. + if machine:state().halted then + builder:add(run_uarch_span(machine), instruction_count - instruction + 1) break end - - big_instruction = big_instruction + 1 end - return initial_state, outer_builder:build() + return initial_state, builder:build() end local function build_big_machine_commitment(base_cycle, log2_stride, log2_stride_count, machine) - machine:advance(base_cycle + 0) - local initial_state = machine:result().state + machine:run(base_cycle) + local initial_state = machine:state().root_hash local builder = MerkleBuilder:new() - local strides = (1 << log2_stride_count) - 1 - - local stride = 0 - while ulte(stride, strides) do - local cycle = ((stride + 1) << (log2_stride - consts.a)) - machine:advance(base_cycle + cycle) - - local state = machine:result() - if not state.halted then - builder:add(state.state) + local instruction_count = arithmetic.max_uint(log2_stride_count) + local instruction = 0 + while ulte(instruction, instruction_count) do + local cycle = ((instruction + 1) << (log2_stride - consts.log2_uarch_span)) + machine:run(base_cycle + cycle) + + if not machine:state().halted then + builder:add(machine:state().root_hash) + instruction = instruction + 1 else -- add this loop plus all remainings - builder:add(state.state, strides - stride + 1) + builder:add(machine:state().root_hash, instruction_count - instruction + 1) break end - - stride = stride + 1 end return initial_state, builder:build() @@ -101,8 +82,8 @@ end local function build_commitment(base_cycle, log2_stride, log2_stride_count, machine_path) local machine = Machine:new_from_path(machine_path) - if log2_stride >= consts.a then - assert(log2_stride - consts.a + log2_stride_count <= 63) + if log2_stride >= consts.log2_uarch_span then + assert(log2_stride - consts.log2_uarch_span + log2_stride_count <= 63) return build_big_machine_commitment(base_cycle, log2_stride, log2_stride_count, machine) else assert(log2_stride == 0) @@ -110,10 +91,67 @@ local function build_commitment(base_cycle, log2_stride, log2_stride_count, mach end end -local path = "program/simple-program" -local initial, tree = build_commitment(0, 0, 64, path) +-- local path = "program/simple-program" +-- -- local initial, tree = build_commitment(0, 0, 64, path) +-- local initial, tree = build_commitment(400, 0, 67, path) -- local initial, tree = build_commitment(0, 64, 63, path) -print(initial, tree.root_hash) +-- print(initial, tree.root_hash) + +-- 0x95ebed36f6708365e01abbec609b89e5b2909b7a127636886afeeffafaf0c2ec +-- 0x0f42278e1dd53a54a4743633bcbc3db7035fd9952eccf5fcad497b6f73c8917c +-- +--0xd4a3511d1c56eb421e64dc218e8d7bf29c5d3ad848306f04c1b7f43b8883b670 +--0x66af9174ab9acb9d47d036b2e735cb9ba31226fd9b06198ce5bc0782c5ca03ff +-- +-- 0x95ebed36f6708365e01abbec609b89e5b2909b7a127636886afeeffafaf0c2ec +-- 0xa27e413a85c252c5664624e5a53c5415148b443983d7101bb3ca88829d1ab269 + + +--[[ +--[[ +a = 2 +b = 2 + +states = 2^b + 1 + +x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) +0 1 2 3 0 1 2 3 0 1 +--]] + + + + +-- local function x(log2_stride, log2_stride_count, machine) +-- local uarch_instruction_count = arithmetic.max_uint(log2_stride_count) +-- local stride = 1 << log2_stride +-- local inner_builder = MerkleBuilder:new() + +-- local ucycle = stride +-- while ulte(ucycle, uarch_instruction_count) do +-- machine:run_uarch(ucycle) +-- local state = machine:state() + +-- if not state.uhalted then +-- inner_builder:add(state.state) +-- ucycle = ucycle + stride +-- else +-- -- add this loop plus all remainings +-- inner_builder:add(state.state, uarch_instruction_count - ucycle + 1) +-- ucycle = uarch_instruction_count +-- break +-- end +-- end + +-- -- At this point, we've added `uarch_instruction_count - 1` hashes to the inner merkle builder. +-- -- Now we do the last state transition (ureset), and add the last state, +-- -- closing in a power-of-two number of leaves (`2^a` leaves). +-- machine:ureset() +-- local state = machine:state() +-- inner_builder:add(state.state) + +-- return inner_builder:build() +-- end +--]] return build_commitment diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua index 100da4923..1d9be3412 100644 --- a/onchain/permissionless-arbitration/offchain/computation/machine.lua +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -2,12 +2,12 @@ local Hash = require "cryptography.hash" local arithmetic = require "utils.arithmetic" local cartesi = require "cartesi" -local ComputationResult = {} -ComputationResult.__index = ComputationResult +local ComputationState = {} +ComputationState.__index = ComputationState -function ComputationResult:new(state, halted, uhalted) +function ComputationState:new(root_hash, halted, uhalted) local r = { - state = state, + root_hash = root_hash, halted = halted, uhalted = uhalted } @@ -15,19 +15,19 @@ function ComputationResult:new(state, halted, uhalted) return r end -function ComputationResult:from_current_machine_state(machine) +function ComputationState:from_current_machine_state(machine) local hash = Hash:from_digest(machine:get_root_hash()) - return ComputationResult:new( + return ComputationState:new( hash, machine:read_iflags_H(), machine:read_uarch_halt_flag() ) end -ComputationResult.__tostring = function(x) +ComputationState.__tostring = function(x) return string.format( - "{state = %s, halted = %s, uhalted = %s}", - x.state, + "{root_hash = %s, halted = %s, uhalted = %s}", + x.root_hash, x.halted, x.uhalted ) @@ -38,6 +38,7 @@ end --- -- +-- TODO Consider removing this abstraction local Machine = {} Machine.__index = Machine @@ -60,36 +61,45 @@ function Machine:new_from_path(path) return b end -function Machine:result() - return ComputationResult:from_current_machine_state(self.machine) +function Machine:state() + return ComputationState:from_current_machine_state(self.machine) end -local function add_and_clamp(x, ...) - for _,v in ipairs {...} do - if arithmetic.ulte(x, x + v) then - x = x + v - else - return -1 - end +local function add_and_clamp(x, y) + if math.ult(x, arithmetic.max_uint64 - y) then + return x + y + else + return arithmetic.max_uint64 end - - return x end -function Machine:advance(cycle, ...) - cycle = add_and_clamp(cycle, ...) - assert(self.cycle <= cycle) - self.machine:run(add_and_clamp(self.start_cycle, cycle)) +function Machine:run(cycle) + assert(arithmetic.ulte(self.cycle, cycle)) + local physical_cycle = add_and_clamp(self.start_cycle, cycle) -- TODO reconsider for lambda + + local machine = self.machine + while not (machine:read_iflags_H() or machine:read_mcycle() == physical_cycle) do + machine:run(physical_cycle) + end + self.cycle = cycle end -function Machine:uadvance(ucycle) +function Machine:run_uarch(ucycle) assert(arithmetic.ulte(self.ucycle, ucycle), string.format("%u, %u", self.ucycle, ucycle)) self.machine:run_uarch(ucycle) self.ucycle = ucycle end +function Machine:increment_uarch() + self.machine:run_uarch(self.ucycle + 1) + self.ucycle = self.ucycle + 1 +end + + + function Machine:ureset() + assert(self.ucycle == arithmetic.max_uint64) self.machine:reset_uarch_state() self.cycle = self.cycle + 1 self.ucycle = 0 diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua index 65f4b2970..9e46c81a9 100644 --- a/onchain/permissionless-arbitration/offchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -1,9 +1,19 @@ +local arithmetic = require "utils.arithmetic" + +local log2_uarch_span = 64 +local log2_emulator_span = 63 + local constants = { levels = 4, - max_cycle = 63, + max_cycle = 63, -- TODO log2step = {24, 14, 7, 0}, heights = {39, 10, 7, 7}, - a = 64, b = 63, + + log2_uarch_span = log2_uarch_span, + uarch_span = arithmetic.max_uint(log2_uarch_span), + + log2_emulator_span = log2_emulator_span, + emulator_span = arithmetic.max_uint(log2_emulator_span), } return constants diff --git a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua index 44c0b1c33..6192a0b26 100644 --- a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua +++ b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua @@ -1,8 +1,10 @@ -local function max_int(k) +local function max_uint(k) assert(k <= 64) return (1 << k) - 1 end +local max_uint64 = max_uint(64) + local function ulte(x, y) return x == y or math.ult(x, y) end @@ -38,7 +40,8 @@ local function semi_sum(a, b) end return { - max_int = max_int, + max_uint = max_uint, + max_uint64 = max_uint64, ulte = ulte, is_pow2 = is_pow2, clz = clz, From 1082c68433f703ceb62aad52aa5e1034b6b600ba Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Tue, 1 Aug 2023 20:35:04 +0300 Subject: [PATCH 08/25] feat(arbitration): update code to latest step --- .../lib/machine-solidity-step | 2 +- .../offchain/constants.lua | 1 - .../tournament/abstracts/LeafTournament.sol | 184 ++++++------------ .../tournament/concretes/BottomTournament.sol | 6 +- .../concretes/SingleLevelTournament.sol | 8 +- .../factories/InnerTournamentFactory.sol | 12 +- .../factories/RootTournamentFactory.sol | 14 +- .../test/MultiTournament.t.sol | 12 +- .../test/Tournament.t.sol | 12 +- .../test/TournamentFactory.t.sol | 10 +- 10 files changed, 74 insertions(+), 187 deletions(-) diff --git a/onchain/permissionless-arbitration/lib/machine-solidity-step b/onchain/permissionless-arbitration/lib/machine-solidity-step index f615814c6..83a5c0309 160000 --- a/onchain/permissionless-arbitration/lib/machine-solidity-step +++ b/onchain/permissionless-arbitration/lib/machine-solidity-step @@ -1 +1 @@ -Subproject commit f615814c6d758b505d55d7336d403e98c0e79135 +Subproject commit 83a5c0309e70d7a30092460f0561baaa81b6e2a8 diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua index 9e46c81a9..6cff43a61 100644 --- a/onchain/permissionless-arbitration/offchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -5,7 +5,6 @@ local log2_emulator_span = 63 local constants = { levels = 4, - max_cycle = 63, -- TODO log2step = {24, 14, 7, 0}, heights = {39, 10, 7, 7}, diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index ce41b2ad3..0d2eb39c6 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -4,9 +4,8 @@ pragma solidity ^0.8.17; import "./Tournament.sol"; import "../../Commitment.sol"; import "../../Merkle.sol"; -import "step/contracts/interfaces/IUArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; -import "step/contracts/interfaces/IMemoryAccessLog.sol"; + +import "step/ready_src/UArchStep.sol"; /// @notice Leaf tournament is the one that seals leaf match abstract contract LeafTournament is Tournament { @@ -17,13 +16,7 @@ abstract contract LeafTournament is Tournament { using Match for Match.Id; using Match for Match.State; - IUArchState immutable stateInterface; - IUArchStep immutable stepInterface; - - constructor(IUArchState _stateInterface, IUArchStep _stepInterface) { - stateInterface = _stateInterface; - stepInterface = _stepInterface; - } + constructor() {} function sealLeafMatch( Match.Id calldata _matchId, @@ -72,14 +65,11 @@ abstract contract LeafTournament is Tournament { _matchState.setInitialState(_initialHash); } - // TODO: do validate access logs, do solidity-step function winLeafMatch( Match.Id calldata _matchId, - IMemoryAccessLog.AccessLogs calldata _accessLogs, - bytes32[] calldata _oldHashes, - bytes32[][] calldata _proofs, Tree.Node _leftNode, - Tree.Node _rightNode + Tree.Node _rightNode, + bytes calldata proofs ) external tournamentNotFinished { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireExist(); @@ -90,119 +80,67 @@ abstract contract LeafTournament is Tournament { _clockOne.requireInitialized(); _clockTwo.requireInitialized(); - require( - _accessLogs.logs.length == _proofs.length, - "proofs length doesn't match" - ); - - { - // workaround stack too deep problem - Machine.Hash _finalState = executeStep( - _matchState.otherParent.toMachineHash(), - _accessLogs, - _oldHashes, - _proofs + Machine.Hash _finalState = Machine.Hash.wrap(metaStep( + _matchState.runningLeafPosition, + AccessLogs.Context( + Tree.Node.unwrap(_matchState.otherParent), + Buffer.Context(proofs, 0) + ) + )); + + ( + Machine.Hash _finalStateOne, + Machine.Hash _finalStateTwo + ) = _matchState.getDivergence(); + + if (_leftNode.join(_rightNode).eq(_matchId.commitmentOne)) { + require( + _finalState.eq(_finalStateOne), + "final state one doesn't match" + ); + + _clockOne.addValidatorEffort(Time.ZERO_DURATION); + pairCommitment( + _matchId.commitmentOne, + _clockOne, + _leftNode, + _rightNode + ); + } else if (_leftNode.join(_rightNode).eq(_matchId.commitmentTwo)) { + require( + _finalState.eq(_finalStateTwo), + "final state two doesn't match" ); - ( - Machine.Hash _finalStateOne, - Machine.Hash _finalStateTwo - ) = _matchState.getDivergence(); - - if (_leftNode.join(_rightNode).eq(_matchId.commitmentOne)) { - require( - _finalState.eq(_finalStateOne), - "final state one doesn't match" - ); - - _clockOne.addValidatorEffort(Time.ZERO_DURATION); - pairCommitment( - _matchId.commitmentOne, - _clockOne, - _leftNode, - _rightNode - ); - } else if (_leftNode.join(_rightNode).eq(_matchId.commitmentTwo)) { - require( - _finalState.eq(_finalStateTwo), - "final state two doesn't match" - ); - - _clockTwo.addValidatorEffort(Time.ZERO_DURATION); - pairCommitment( - _matchId.commitmentTwo, - _clockTwo, - _leftNode, - _rightNode - ); - } else { - revert("wrong left/right nodes for step"); - } + _clockTwo.addValidatorEffort(Time.ZERO_DURATION); + pairCommitment( + _matchId.commitmentTwo, + _clockTwo, + _leftNode, + _rightNode + ); + } else { + revert("wrong left/right nodes for step"); } delete matches[_matchId.hashFromId()]; } - function executeStep( - Machine.Hash _initialState, - IMemoryAccessLog.AccessLogs memory _accessLogs, - bytes32[] memory _oldHashes, - bytes32[][] memory _proofs - ) internal returns (Machine.Hash) { - uint256 _writeCount = 0; - Machine.Hash _finalState = _initialState; - for (uint256 _i = 0; _i < _accessLogs.logs.length; _i++) { - if ( - _accessLogs.logs[_i].accessType == - IMemoryAccessLog.AccessType.Write - ) { - // validate write proofs with old value, and update the machine hash with new value - // developer should make sure the _oldHashes array matches the write operations in the _accesses array - require( - _finalState.eq( - Machine.Hash.wrap( - Merkle.getRootWithHash( - _accessLogs.logs[_i].position, - _oldHashes[_writeCount], - _proofs[_i] - ) - ) - ), - "machine hash doesn't match" - ); - - _finalState = Machine.Hash.wrap( - Merkle.getRootWithValue( - _accessLogs.logs[_i].position, - _accessLogs.logs[_i].val, - _proofs[_i] - ) - ); - - _writeCount++; - } else { - // validate read proofs - require( - _finalState.eq( - Machine.Hash.wrap( - Merkle.getRootWithValue( - _accessLogs.logs[_i].position, - _accessLogs.logs[_i].val, - _proofs[_i] - ) - ) - ), - "machine hash doesn't match" - ); - } - } - // call machine-solidity-step to replay accessLogs - IUArchState.State memory _state = IUArchState.State( - stateInterface, - _accessLogs - ); - stepInterface.step(_state); - - return _finalState; - } + // TODO: move to step repo + // TODO: add ureset + function metaStep(uint256 counter, AccessLogs.Context memory accessLogs) + internal + pure + returns (bytes32) + { + uint256 mask = (1 << 64) - 1; + if (counter & mask == mask) { + // reset + revert("RESET UNIMPLEMENTED"); + } else { + UArchStep.step(accessLogs); + bytes32 machineState = accessLogs.currentRootHash; + return machineState; + } + } } diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol index fb7fbd070..b702c9396 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.17; import "../abstracts/LeafTournament.sol"; import "../abstracts/NonRootTournament.sol"; -import "step/contracts/interfaces/IUArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; /// @notice Bottom tournament of a multi-level instance contract BottomTournament is LeafTournament, NonRootTournament { @@ -17,11 +15,9 @@ contract BottomTournament is LeafTournament, NonRootTournament { Time.Duration _allowance, uint256 _startCycle, uint64 _level, - IUArchState _stateInterface, - IUArchStep _stepInterface, NonLeafTournament _parent ) - LeafTournament(_stateInterface, _stepInterface) + LeafTournament() NonRootTournament( _initialHash, _contestedCommitmentOne, diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol index 00b238c15..e7e7124b6 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol @@ -3,16 +3,12 @@ pragma solidity ^0.8.17; import "../abstracts/RootTournament.sol"; import "../abstracts/LeafTournament.sol"; -import "step/contracts/interfaces/IUArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; contract SingleLevelTournament is LeafTournament, RootTournament { constructor( - Machine.Hash _initialHash, - IUArchState _stateInterface, - IUArchStep _stepInterface + Machine.Hash _initialHash ) - LeafTournament(_stateInterface, _stepInterface) + LeafTournament() RootTournament(_initialHash) {} } diff --git a/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol index fd60db430..39e93c2fb 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol @@ -4,17 +4,9 @@ pragma solidity ^0.8.17; import "../interfaces/IInnerTournamentFactory.sol"; import "../concretes/MiddleTournament.sol"; import "../concretes/BottomTournament.sol"; -import "step/contracts/interfaces/IUArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; contract InnerTournamentFactory is IInnerTournamentFactory { - IUArchState immutable stateInterface; - IUArchStep immutable stepInterface; - - constructor(IUArchState _stateInterface, IUArchStep _stepInterface) { - stateInterface = _stateInterface; - stepInterface = _stepInterface; - } + constructor() {} function instantiateInner( Machine.Hash _initialHash, @@ -100,8 +92,6 @@ contract InnerTournamentFactory is IInnerTournamentFactory { _allowance, _startCycle, _level, - stateInterface, - stepInterface, NonLeafTournament(msg.sender) ); diff --git a/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol index 70f3fc13f..c10a80aa2 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol @@ -4,31 +4,21 @@ pragma solidity ^0.8.17; import "../interfaces/IRootTournamentFactory.sol"; import "../concretes/SingleLevelTournament.sol"; import "../concretes/TopTournament.sol"; -import "step/contracts/interfaces/IUArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; contract RootTournamentFactory is IRootTournamentFactory { IInnerTournamentFactory immutable innerFactory; - IUArchState immutable stateInterface; - IUArchStep immutable stepInterface; constructor( - IInnerTournamentFactory _innerFactory, - IUArchState _stateInterface, - IUArchStep _stepInterface + IInnerTournamentFactory _innerFactory ) { innerFactory = _innerFactory; - stateInterface = _stateInterface; - stepInterface = _stepInterface; } function instantiateSingle( Machine.Hash _initialHash ) external override returns (RootTournament) { SingleLevelTournament _tournament = new SingleLevelTournament( - _initialHash, - stateInterface, - stepInterface + _initialHash ); emit rootCreated(_tournament); diff --git a/onchain/permissionless-arbitration/test/MultiTournament.t.sol b/onchain/permissionless-arbitration/test/MultiTournament.t.sol index 0c5b2e0e5..793ddd812 100644 --- a/onchain/permissionless-arbitration/test/MultiTournament.t.sol +++ b/onchain/permissionless-arbitration/test/MultiTournament.t.sol @@ -17,10 +17,6 @@ import "./Util.sol"; import "src/tournament/factories/RootTournamentFactory.sol"; import "src/tournament/factories/InnerTournamentFactory.sol"; import "src/CanonicalConstants.sol"; -import "step/contracts/UArchStep.sol"; -import "step/contracts/UArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; -import "step/contracts/interfaces/IUArchState.sol"; pragma solidity ^0.8.0; @@ -34,8 +30,6 @@ contract MultiTournamentTest is Test { // player 0, player 1, and player 2 Tree.Node[][3] playerNodes; - IUArchState immutable state; - IUArchStep immutable step; IRootTournamentFactory immutable rootFactory; IInnerTournamentFactory immutable innerFactory; TopTournament topTournament; @@ -49,10 +43,8 @@ contract MultiTournamentTest is Test { event newInnerTournament(Match.IdHash indexed, NonRootTournament); constructor() { - state = new UArchState(); - step = new UArchStep(); - innerFactory = new InnerTournamentFactory(state, step); - rootFactory = new RootTournamentFactory(innerFactory, state, step); + innerFactory = new InnerTournamentFactory(); + rootFactory = new RootTournamentFactory(innerFactory); } function setUp() public { diff --git a/onchain/permissionless-arbitration/test/Tournament.t.sol b/onchain/permissionless-arbitration/test/Tournament.t.sol index 6b49ea838..810ed22a4 100644 --- a/onchain/permissionless-arbitration/test/Tournament.t.sol +++ b/onchain/permissionless-arbitration/test/Tournament.t.sol @@ -17,10 +17,6 @@ import "./Util.sol"; import "src/tournament/factories/RootTournamentFactory.sol"; import "src/tournament/factories/InnerTournamentFactory.sol"; import "src/CanonicalConstants.sol"; -import "step/contracts/UArchStep.sol"; -import "step/contracts/UArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; -import "step/contracts/interfaces/IUArchState.sol"; pragma solidity ^0.8.0; @@ -35,8 +31,6 @@ contract TournamentTest is Test { Tree.Node[][3] playerNodes; Tree.Node constant ONE_NODE = Tree.Node.wrap(bytes32(uint256(1))); - IUArchState immutable state; - IUArchStep immutable step; IRootTournamentFactory immutable rootFactory; IInnerTournamentFactory immutable innerFactory; TopTournament topTournament; @@ -50,10 +44,8 @@ contract TournamentTest is Test { event newInnerTournament(Match.IdHash indexed, NonRootTournament); constructor() { - state = new UArchState(); - step = new UArchStep(); - innerFactory = new InnerTournamentFactory(state, step); - rootFactory = new RootTournamentFactory(innerFactory, state, step); + innerFactory = new InnerTournamentFactory(); + rootFactory = new RootTournamentFactory(innerFactory); } function setUp() public { diff --git a/onchain/permissionless-arbitration/test/TournamentFactory.t.sol b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol index d95490b71..6ef904dd2 100644 --- a/onchain/permissionless-arbitration/test/TournamentFactory.t.sol +++ b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol @@ -17,22 +17,16 @@ import "src/tournament/abstracts/RootTournament.sol"; import "src/tournament/factories/RootTournamentFactory.sol"; import "src/tournament/factories/InnerTournamentFactory.sol"; import "src/CanonicalConstants.sol"; -import "step/contracts/UArchStep.sol"; -import "step/contracts/UArchState.sol"; -import "step/contracts/interfaces/IUArchStep.sol"; -import "step/contracts/interfaces/IUArchState.sol"; pragma solidity ^0.8.0; contract TournamentFactoryTest is Test { IRootTournamentFactory rootFactory; IInnerTournamentFactory innerFactory; - IUArchState state = new UArchState(); - IUArchStep step = new UArchStep(); function setUp() public { - innerFactory = new InnerTournamentFactory(state, step); - rootFactory = new RootTournamentFactory(innerFactory, state, step); + innerFactory = new InnerTournamentFactory(); + rootFactory = new RootTournamentFactory(innerFactory); } function testRootTournament() public { From d07e7f81882f8d30cf46f97d47783b93bfd19b4d Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 3 Aug 2023 00:26:50 +0300 Subject: [PATCH 09/25] feat(arbitration): redesign factories --- .../permissionless-arbitration/.dockerignore | 2 + .../{offchain => }/Dockerfile | 12 +- .../offchain/.dockerignore | 2 - .../offchain/blockchain/node.lua | 111 +++++++++++++----- .../offchain/entrypoint.lua | 11 +- .../src/Commitment.sol | 2 +- .../permissionless-arbitration/src/Match.sol | 24 ++-- .../tournament/abstracts/LeafTournament.sol | 48 +++++--- .../abstracts/NonLeafTournament.sol | 52 +++++++- .../tournament/concretes/MiddleTournament.sol | 7 +- .../tournament/concretes/TopTournament.sol | 10 +- .../factories/BottomTournamentFactory.sol | 34 ++++++ .../factories/ITournamentFactory.sol | 38 ++++++ .../factories/InnerTournamentFactory.sol | 100 ---------------- .../factories/MiddleTournamentFactory.sol | 41 +++++++ .../factories/RootTournamentFactory.sol | 41 ------- .../SingleLevelTournamentFactory.sol | 18 +++ .../factories/TopTournamentFactory.sol | 19 +++ .../factories/TournamentFactory.sol | 104 ++++++++++++++++ .../interfaces/IInnerTournamentFactory.sol | 21 ---- .../interfaces/IMultiTournamentFactory.sol | 24 ---- .../interfaces/IRootTournamentFactory.sol | 17 --- .../interfaces/ISingleTournamentFactory.sol | 13 -- .../test/MultiTournament.t.sol | 19 ++- .../test/Tournament.t.sol | 15 +-- .../test/TournamentFactory.t.sol | 19 ++- .../permissionless-arbitration/test/Util.sol | 21 +++- 27 files changed, 506 insertions(+), 319 deletions(-) create mode 100644 onchain/permissionless-arbitration/.dockerignore rename onchain/permissionless-arbitration/{offchain => }/Dockerfile (55%) delete mode 100644 onchain/permissionless-arbitration/offchain/.dockerignore create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol create mode 100644 onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol delete mode 100644 onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol diff --git a/onchain/permissionless-arbitration/.dockerignore b/onchain/permissionless-arbitration/.dockerignore new file mode 100644 index 000000000..8c4ed6c48 --- /dev/null +++ b/onchain/permissionless-arbitration/.dockerignore @@ -0,0 +1,2 @@ +offchain/program/simple-linux-program/ +offchain/program/simple-program/ diff --git a/onchain/permissionless-arbitration/offchain/Dockerfile b/onchain/permissionless-arbitration/Dockerfile similarity index 55% rename from onchain/permissionless-arbitration/offchain/Dockerfile rename to onchain/permissionless-arbitration/Dockerfile index d5a88a2c4..8f00fc4ef 100644 --- a/onchain/permissionless-arbitration/offchain/Dockerfile +++ b/onchain/permissionless-arbitration/Dockerfile @@ -3,8 +3,12 @@ RUN apt-get -y update; apt-get -y install curl RUN curl -sSL https://github.com/foundry-rs/foundry/releases/download/nightly/foundry_nightly_linux_$(dpkg --print-architecture).tar.gz | \ tar -zx -C /usr/local/bin -ADD ./ ./src/ -WORKDIR "./src" +ADD foundry.toml ./project/ +ADD lib ./project/lib/ +ADD src ./project/src/ +WORKDIR "./project" +RUN forge build -RUN chmod +x ./entrypoint.lua -ENTRYPOINT ["./entrypoint.lua"] +ADD ./offchain/ ./offchain/ +RUN chmod +x ./offchain/entrypoint.lua +ENTRYPOINT ["./offchain/entrypoint.lua"] diff --git a/onchain/permissionless-arbitration/offchain/.dockerignore b/onchain/permissionless-arbitration/offchain/.dockerignore deleted file mode 100644 index 7e99db842..000000000 --- a/onchain/permissionless-arbitration/offchain/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -program/simple-linux-program/ -program/simple-program/ diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index d1ad2af50..dce2ea042 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -63,57 +63,110 @@ end local function deploy_contracts(endpoint, deployer, initial_hash) - -- Deploy Inner Factory - print "Deploying inner factory..." + -- + -- Deploy Single Level Factory + print "Deploying Single Level factory..." - local cmd_inner = string.format( - [[sh -c "forge create InnerTournamentFactory --rpc-url=%s --private-key=%s"]], + local cmd_sl = string.format( + [[sh -c "forge create SingleLevelTournamentFactory --rpc-url=%s --private-key=%s"]], endpoint, deployer ) - local handle_inner = io.popen(cmd_inner) - assert(handle_inner, "`popen` returned nil handle") + local handle_sl = io.popen(cmd_sl) + assert(handle_sl, "`popen` returned nil handle") - local _, _, inner_factory_address = handle_inner:read("*a"):find("Deployed to: (0x%x+)") - assert(inner_factory_address, "deployment failed, factory_address is nil") - print("Inner factory deployed at", inner_factory_address) - handle_inner:close() + local _, _, sl_factory_address = handle_sl:read("*a"):find("Deployed to: (0x%x+)") + assert(sl_factory_address, "deployment failed, factory_address is nil") + print("Single Level factory deployed at", sl_factory_address) + handle_sl:close() -- - -- Deploy Root Factory - print "Deploying root factory..." + -- Deploy top Factory + print "Deploying Top factory..." - local cmd_root = string.format( - [[sh -c "forge create RootTournamentFactory --rpc-url=%s --private-key=%s --constructor-args %s"]], - endpoint, deployer, inner_factory_address + local cmd_top = string.format( + [[sh -c "forge create TopTournamentFactory --rpc-url=%s --private-key=%s"]], + endpoint, deployer ) - local handle_root = io.popen(cmd_root) - assert(handle_root, "`popen` returned nil handle") + local handle_top = io.popen(cmd_top) + assert(handle_top, "`popen` returned nil handle") - local _, _, root_factory_address = handle_root:read("*a"):find("Deployed to: (0x%x+)") - assert(root_factory_address, "deployment failed, factory_address is nil") - print("Root factory deployed at", root_factory_address) - handle_root:close() + local _, _, top_factory_address = handle_top:read("*a"):find("Deployed to: (0x%x+)") + assert(top_factory_address, "deployment failed, factory_address is nil") + print("Top factory deployed at", top_factory_address) + handle_top:close() + + -- + -- Deploy middle Factory + print "Deploying Middle factory..." + + local cmd_mid = string.format( + [[sh -c "forge create MiddleTournamentFactory --rpc-url=%s --private-key=%s"]], + endpoint, deployer + ) + + local handle_mid = io.popen(cmd_mid) + assert(handle_mid, "`popen` returned nil handle") + + local _, _, mid_factory_address = handle_mid:read("*a"):find("Deployed to: (0x%x+)") + assert(mid_factory_address, "deployment failed, factory_address is nil") + print("Middle factory deployed at", mid_factory_address) + handle_mid:close() + + -- + -- Deploy bottom Factory + print "Deploying Bottom factory..." + + local cmd_bot = string.format( + [[sh -c "forge create BottomTournamentFactory --rpc-url=%s --private-key=%s"]], + endpoint, deployer + ) + + local handle_bot = io.popen(cmd_bot) + assert(handle_bot, "`popen` returned nil handle") + + local _, _, bot_factory_address = handle_bot:read("*a"):find("Deployed to: (0x%x+)") + assert(bot_factory_address, "deployment failed, factory_address is nil") + print("Bottom factory deployed at", bot_factory_address) + handle_bot:close() + + + -- + -- Deploy Tournament Factory + print "Deploying Tournament factory..." + + local cmd_tournament = string.format( + [[sh -c "forge create TournamentFactory --rpc-url=%s --private-key=%s --constructor-args %s %s %s %s"]], + endpoint, deployer, sl_factory_address, top_factory_address, mid_factory_address, bot_factory_address + ) + + local handle_tournament = io.popen(cmd_tournament) + assert(handle_tournament, "`popen` returned nil handle") + + local _, _, tournament_factory_address = handle_tournament:read("*a"):find("Deployed to: (0x%x+)") + assert(tournament_factory_address, "deployment failed, factory_address is nil") + print("tournament factory deployed at", tournament_factory_address) + handle_tournament:close() -- -- Instantiate Root Tournament - print "Instantiate root contract..." + print "Instantiate root tournament contract..." - local cmd2 = string.format( - [[cast send --private-key "%s" --rpc-url "%s" "%s" "instantiateTopOfMultiple(bytes32)" "%s"]], - deployer, endpoint, root_factory_address, initial_hash + local cmd_root = string.format( + [[cast send --private-key "%s" --rpc-url "%s" "%s" "instantiateTop(bytes32)" "%s"]], + deployer, endpoint, tournament_factory_address, initial_hash ) - local handle2 = io.popen(cmd2) - assert(handle2, "`popen` returned nil handle") + local handle_root = io.popen(cmd_root) + assert(handle_root, "`popen` returned nil handle") - local _, _, a = handle2:read("*a"):find [["data":"0x000000000000000000000000(%x+)"]] + local _, _, a = handle_root:read("*a"):find [["data":"0x000000000000000000000000(%x+)"]] local address = "0x" .. a assert(address, "deployment failed, address is nil") print("Contract deployed at", address) - handle2:close() + handle_root:close() return address end diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index f7da0a2a2..4ad7e2ec8 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -1,17 +1,24 @@ #!/usr/bin/lua package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" print "Hello, world!" -os.execute "cd program && ./gen_machine_simple.sh" +os.execute "cd offchain/program && ./gen_machine_simple.sh" -- os.execute "jsonrpc-remote-cartesi-machine --server-address=localhost:8080 &" -- os.execute "sleep 2" -- require "cryptography.merkle_builder" -require "computation.commitment" +-- require "computation.commitment" -- require "computation.machine_test" +local Blockchain = require "blockchain.node" + +local bc = Blockchain:new(100) +local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" +bc:deploy_contract(initial_hash) + -- local utils = require "utils" -- local cartesi = {} diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index 01df9569f..b3804b555 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -25,7 +25,7 @@ library Commitment { function proveHash( Tree.Node root, - uint64 position, + uint256 position, Machine.Hash hash, bytes32[] calldata hashProof ) internal pure { diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index 903d346e8..a622f37f4 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -60,7 +60,7 @@ library Match { Tree.Node rightNode; // Once match is done, leftNode and rightNode change meaning // and contains contested final states. - uint64 runningLeafPosition; + uint256 runningLeafPosition; uint64 height; uint64 currentHeight; } @@ -81,8 +81,8 @@ library Match { leftNodeOfTwo, rightNodeOfTwo, 0, - height, // TODO - height // TODO + height, + height ); return (matchId.hashFromId(), state); @@ -111,7 +111,7 @@ library Match { state.leftNode = newLeftNode; state.rightNode = newRightNode; - state.runningLeafPosition += uint64(1 << state.currentHeight); // TODO: verify + state.runningLeafPosition += 1 << state.currentHeight; // TODO: verify state.currentHeight--; } @@ -157,13 +157,22 @@ library Match { } function getDivergence( - State storage state + State storage state, + uint256 startCycle, + uint64 level ) internal view - returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + returns ( + Machine.Hash agreeHash, + uint256 agreeCycle, + Machine.Hash finalStateOne, + Machine.Hash finalStateTwo + ) { assert(state.currentHeight == 0); + agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); + agreeCycle = state.toCycle(startCycle, level); if (state.runningLeafPosition % 2 == 0) { // divergence was set on left leaf @@ -190,6 +199,7 @@ library Match { State storage state, Machine.Hash initialState ) internal { + assert(state.currentHeight == 0); state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); } @@ -267,7 +277,7 @@ library Match { uint64 log2step ) internal pure returns (uint256) { uint256 step = 1 << log2step; - uint256 leafPosition = state.runningLeafPosition + 1; // +1 is implicit initialHash + uint256 leafPosition = state.runningLeafPosition; return base + (leafPosition * step); // TODO verify } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index 0d2eb39c6..97b1030be 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -56,7 +56,7 @@ abstract contract LeafTournament is Tournament { require(_initialHash.eq(initialHash), "initial hash incorrect"); } else { _matchId.commitmentOne.proveHash( - _matchState.runningLeafPosition - 1, + _matchState.runningLeafPosition, _initialHash, _initialHashProof ); @@ -80,18 +80,18 @@ abstract contract LeafTournament is Tournament { _clockOne.requireInitialized(); _clockTwo.requireInitialized(); - Machine.Hash _finalState = Machine.Hash.wrap(metaStep( - _matchState.runningLeafPosition, - AccessLogs.Context( - Tree.Node.unwrap(_matchState.otherParent), - Buffer.Context(proofs, 0) - ) - )); - ( + Machine.Hash _agreeHash, + uint256 _agreeCycle, Machine.Hash _finalStateOne, Machine.Hash _finalStateTwo - ) = _matchState.getDivergence(); + ) = _matchState.getDivergence(startCycle, level); + + Machine.Hash _finalState = runMetaStep( + _agreeHash, + _agreeCycle, + proofs + ); if (_leftNode.join(_rightNode).eq(_matchId.commitmentOne)) { require( @@ -126,21 +126,39 @@ abstract contract LeafTournament is Tournament { delete matches[_matchId.hashFromId()]; } + function runMetaStep(Machine.Hash machineState, uint256 counter, bytes memory proofs) + internal + pure + returns (Machine.Hash) + { + return Machine.Hash.wrap(metaStep( + Machine.Hash.unwrap(machineState), + counter, + proofs + )); + } + // TODO: move to step repo // TODO: add ureset - function metaStep(uint256 counter, AccessLogs.Context memory accessLogs) + function metaStep(bytes32 machineState, uint256 counter, bytes memory proofs) internal pure returns (bytes32) - { + { + // TODO: create a more convinient constructor. + AccessLogs.Context memory accessLogs = AccessLogs.Context( + machineState, + Buffer.Context(proofs, 0) + ); + uint256 mask = (1 << 64) - 1; if (counter & mask == mask) { // reset revert("RESET UNIMPLEMENTED"); } else { UArchStep.step(accessLogs); - bytes32 machineState = accessLogs.currentRootHash; - return machineState; + bytes32 newMachineState = accessLogs.currentRootHash; + return newMachineState; } - } + } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index f140d0353..b03edd76d 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; -import "../interfaces/IInnerTournamentFactory.sol"; +import "../factories/ITournamentFactory.sol"; import "./Tournament.sol"; import "./NonRootTournament.sol"; @@ -20,7 +20,7 @@ abstract contract NonLeafTournament is Tournament { // Constants // - IInnerTournamentFactory immutable innerFactory; + ITournamentFactory immutable tournamentFactory; // // Storage @@ -49,8 +49,8 @@ abstract contract NonLeafTournament is Tournament { // Constructor // - constructor(IInnerTournamentFactory _innerFactory) { - innerFactory = _innerFactory; + constructor(ITournamentFactory _tournamentFactory) { + tournamentFactory = _tournamentFactory; } function sealInnerMatchAndCreateInnerTournament( @@ -98,7 +98,7 @@ abstract contract NonLeafTournament is Tournament { ); } - NonRootTournament _inner = innerFactory.instantiateInner( + NonRootTournament _inner = instantiateInner( _initialHash, _matchId.commitmentOne, _finalStateOne, @@ -160,4 +160,46 @@ abstract contract NonLeafTournament is Tournament { updateParentTournamentDelay(_delay); } } + + function instantiateInner( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) private returns (NonRootTournament) { + // the inner tournament is bottom tournament at last level + // else instantiate middle tournament + Tournament _tournament; + if (_level == ArbitrationConstants.LEVELS - 1) { + _tournament = tournamentFactory.instantiateBottom( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level + ); + } else { + _tournament = tournamentFactory.instantiateMiddle( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level + ); + } + + return NonRootTournament(address(_tournament)); + } + + } diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol index 4107d1570..1accf9c1d 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.17; import "../abstracts/NonLeafTournament.sol"; import "../abstracts/NonRootTournament.sol"; +import "../factories/TournamentFactory.sol"; + /// @notice Middle tournament is non-top, non-bottom of a multi-level instance contract MiddleTournament is NonLeafTournament, NonRootTournament { constructor( @@ -15,9 +17,10 @@ contract MiddleTournament is NonLeafTournament, NonRootTournament { Time.Duration _allowance, uint256 _startCycle, uint64 _level, - NonLeafTournament _parent + NonLeafTournament _parent, + TournamentFactory _tournamentFactory ) - NonLeafTournament(IInnerTournamentFactory(msg.sender)) + NonLeafTournament(_tournamentFactory) NonRootTournament( _initialHash, _contestedCommitmentOne, diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol index ea14567b6..da9368f05 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol @@ -4,13 +4,17 @@ pragma solidity ^0.8.17; import "../abstracts/RootTournament.sol"; import "../abstracts/NonLeafTournament.sol"; +import "../factories/TournamentFactory.sol"; + +import "../../Machine.sol"; + /// @notice Top tournament of a multi-level instance contract TopTournament is NonLeafTournament, RootTournament { constructor( - IInnerTournamentFactory _innerFactory, - Machine.Hash _initialHash + Machine.Hash _initialHash, + TournamentFactory _factory ) - NonLeafTournament(_innerFactory) + NonLeafTournament(_factory) RootTournament(_initialHash) {} } diff --git a/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol new file mode 100644 index 000000000..290a77ad9 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../concretes/BottomTournament.sol"; + +contract BottomTournamentFactory { + constructor() {} + + function instantiate( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level, + NonLeafTournament _parent + ) external returns (BottomTournament) { + BottomTournament _tournament = new BottomTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + _parent + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol new file mode 100644 index 000000000..eca00ca4e --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/Tournament.sol"; + +interface ITournamentFactory { + event rootCreated(Tournament); + + function instantiateSingleLevel( + Machine.Hash _initialHash + ) external returns (Tournament); + + function instantiateTop( + Machine.Hash _initialHash + ) external returns (Tournament); + + function instantiateMiddle( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external returns (Tournament); + + function instantiateBottom( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external returns (Tournament); +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol deleted file mode 100644 index 39e93c2fb..000000000 --- a/onchain/permissionless-arbitration/src/tournament/factories/InnerTournamentFactory.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import "../interfaces/IInnerTournamentFactory.sol"; -import "../concretes/MiddleTournament.sol"; -import "../concretes/BottomTournament.sol"; - -contract InnerTournamentFactory is IInnerTournamentFactory { - constructor() {} - - function instantiateInner( - Machine.Hash _initialHash, - Tree.Node _contestedCommitmentOne, - Machine.Hash _contestedFinalStateOne, - Tree.Node _contestedCommitmentTwo, - Machine.Hash _contestedFinalStateTwo, - Time.Duration _allowance, - uint256 _startCycle, - uint64 _level - ) external override returns (NonRootTournament) { - // the inner tournament is bottom tournament at last level - // else instantiate middle tournament - NonRootTournament _tournament; - if (_level == ArbitrationConstants.LEVELS - 1) { - _tournament = instantiateBottom( - _initialHash, - _contestedCommitmentOne, - _contestedFinalStateOne, - _contestedCommitmentTwo, - _contestedFinalStateTwo, - _allowance, - _startCycle, - _level - ); - } else { - _tournament = instantiateMiddle( - _initialHash, - _contestedCommitmentOne, - _contestedFinalStateOne, - _contestedCommitmentTwo, - _contestedFinalStateTwo, - _allowance, - _startCycle, - _level - ); - } - - return _tournament; - } - - function instantiateMiddle( - Machine.Hash _initialHash, - Tree.Node _contestedCommitmentOne, - Machine.Hash _contestedFinalStateOne, - Tree.Node _contestedCommitmentTwo, - Machine.Hash _contestedFinalStateTwo, - Time.Duration _allowance, - uint256 _startCycle, - uint64 _level - ) internal returns (NonRootTournament) { - MiddleTournament _tournament = new MiddleTournament( - _initialHash, - _contestedCommitmentOne, - _contestedFinalStateOne, - _contestedCommitmentTwo, - _contestedFinalStateTwo, - _allowance, - _startCycle, - _level, - NonLeafTournament(msg.sender) - ); - - return _tournament; - } - - function instantiateBottom( - Machine.Hash _initialHash, - Tree.Node _contestedCommitmentOne, - Machine.Hash _contestedFinalStateOne, - Tree.Node _contestedCommitmentTwo, - Machine.Hash _contestedFinalStateTwo, - Time.Duration _allowance, - uint256 _startCycle, - uint64 _level - ) internal returns (NonRootTournament) { - BottomTournament _tournament = new BottomTournament( - _initialHash, - _contestedCommitmentOne, - _contestedFinalStateOne, - _contestedCommitmentTwo, - _contestedFinalStateTwo, - _allowance, - _startCycle, - _level, - NonLeafTournament(msg.sender) - ); - - return _tournament; - } -} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol new file mode 100644 index 000000000..7ed94beec --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "./TournamentFactory.sol"; +import "../abstracts/NonLeafTournament.sol"; +import "../concretes/MiddleTournament.sol"; + +import "../../Machine.sol"; +import "../../Tree.sol"; +import "../../Time.sol"; + +contract MiddleTournamentFactory { + constructor() {} + + function instantiate( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level, + NonLeafTournament _parent + ) external returns (MiddleTournament) { + MiddleTournament _tournament = new MiddleTournament( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + _parent, + TournamentFactory(msg.sender) + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol deleted file mode 100644 index c10a80aa2..000000000 --- a/onchain/permissionless-arbitration/src/tournament/factories/RootTournamentFactory.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import "../interfaces/IRootTournamentFactory.sol"; -import "../concretes/SingleLevelTournament.sol"; -import "../concretes/TopTournament.sol"; - -contract RootTournamentFactory is IRootTournamentFactory { - IInnerTournamentFactory immutable innerFactory; - - constructor( - IInnerTournamentFactory _innerFactory - ) { - innerFactory = _innerFactory; - } - - function instantiateSingle( - Machine.Hash _initialHash - ) external override returns (RootTournament) { - SingleLevelTournament _tournament = new SingleLevelTournament( - _initialHash - ); - - emit rootCreated(_tournament); - - return _tournament; - } - - function instantiateTopOfMultiple( - Machine.Hash _initialHash - ) external override returns (RootTournament) { - TopTournament _tournament = new TopTournament( - innerFactory, - _initialHash - ); - - emit rootCreated(_tournament); - - return _tournament; - } -} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol new file mode 100644 index 000000000..ccdee5678 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../concretes/SingleLevelTournament.sol"; + +contract SingleLevelTournamentFactory { + constructor() {} + + function instantiate( + Machine.Hash _initialHash + ) external returns (SingleLevelTournament) { + SingleLevelTournament _tournament = new SingleLevelTournament( + _initialHash + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol new file mode 100644 index 000000000..5c0999ac0 --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../concretes/TopTournament.sol"; + +contract TopTournamentFactory { + constructor() {} + + function instantiate( + Machine.Hash _initialHash + ) external returns (TopTournament) { + TopTournament _tournament = new TopTournament( + _initialHash, + TournamentFactory(msg.sender) + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol new file mode 100644 index 000000000..c483d384d --- /dev/null +++ b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "../abstracts/NonRootTournament.sol"; +import "../concretes/TopTournament.sol"; +import "../concretes/MiddleTournament.sol"; +import "../concretes/BottomTournament.sol"; +import "../concretes/SingleLevelTournament.sol"; + +import "./TopTournamentFactory.sol"; +import "./MiddleTournamentFactory.sol"; +import "./BottomTournamentFactory.sol"; +import "./SingleLevelTournamentFactory.sol"; + +contract TournamentFactory is ITournamentFactory { + SingleLevelTournamentFactory immutable singleLevelFactory; + TopTournamentFactory immutable topFactory; + MiddleTournamentFactory immutable middleFactory; + BottomTournamentFactory immutable bottomFactory; + + constructor( + SingleLevelTournamentFactory _singleLevelFactory, + TopTournamentFactory _topFactory, + MiddleTournamentFactory _middleFactory, + BottomTournamentFactory _bottomFactory + ) { + topFactory = _topFactory; + middleFactory = _middleFactory; + bottomFactory = _bottomFactory; + singleLevelFactory = _singleLevelFactory; + } + + function instantiateSingleLevel( + Machine.Hash _initialHash + ) external override returns (Tournament) { + SingleLevelTournament _tournament = singleLevelFactory.instantiate( + _initialHash + ); + emit rootCreated(_tournament); + + return _tournament; + } + + function instantiateTop( + Machine.Hash _initialHash + ) external override returns (Tournament) { + TopTournament _tournament = topFactory.instantiate( + _initialHash + ); + emit rootCreated(_tournament); + + return _tournament; + } + + function instantiateMiddle( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external override returns (Tournament) { + MiddleTournament _tournament = middleFactory.instantiate( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + NonLeafTournament(msg.sender) + ); + + return _tournament; + } + + function instantiateBottom( + Machine.Hash _initialHash, + Tree.Node _contestedCommitmentOne, + Machine.Hash _contestedFinalStateOne, + Tree.Node _contestedCommitmentTwo, + Machine.Hash _contestedFinalStateTwo, + Time.Duration _allowance, + uint256 _startCycle, + uint64 _level + ) external override returns (Tournament) { + BottomTournament _tournament = bottomFactory.instantiate( + _initialHash, + _contestedCommitmentOne, + _contestedFinalStateOne, + _contestedCommitmentTwo, + _contestedFinalStateTwo, + _allowance, + _startCycle, + _level, + NonLeafTournament(msg.sender) + ); + + return _tournament; + } +} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol deleted file mode 100644 index 1ca474c23..000000000 --- a/onchain/permissionless-arbitration/src/tournament/interfaces/IInnerTournamentFactory.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0 <0.9; - -import "../../Machine.sol"; -import "../../Tree.sol"; -import "../../Time.sol"; - -import "../abstracts/NonRootTournament.sol"; - -interface IInnerTournamentFactory { - function instantiateInner( - Machine.Hash _initialHash, - Tree.Node _contestedCommitmentOne, - Machine.Hash _contestedFinalStateOne, - Tree.Node _contestedCommitmentTwo, - Machine.Hash _contestedFinalStateTwo, - Time.Duration _allowance, - uint256 _startCycle, - uint64 _level - ) external returns (NonRootTournament); -} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol deleted file mode 100644 index 5f2fdc120..000000000 --- a/onchain/permissionless-arbitration/src/tournament/interfaces/IMultiTournamentFactory.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0 <0.9; - -import "../abstracts/RootTournament.sol"; -import "../abstracts/NonRootTournament.sol"; - -interface IMultiTournamentFactory { - event rootCreated(RootTournament); - - function instantiateTop( - Machine.Hash _initialHash - ) external returns (RootTournament); - - function instantiateInner( - Machine.Hash _initialHash, - Tree.Node _contestedCommitmentOne, - Machine.Hash _contestedFinalStateOne, - Tree.Node _contestedCommitmentTwo, - Machine.Hash _contestedFinalStateTwo, - Time.Duration _allowance, - uint256 _startCycle, - uint64 _level - ) external returns (NonRootTournament); -} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol deleted file mode 100644 index ac383c3d5..000000000 --- a/onchain/permissionless-arbitration/src/tournament/interfaces/IRootTournamentFactory.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0 <0.9; - -import "../interfaces/IInnerTournamentFactory.sol"; -import "../abstracts/RootTournament.sol"; - -interface IRootTournamentFactory { - event rootCreated(RootTournament); - - function instantiateSingle( - Machine.Hash _initialHash - ) external returns (RootTournament); - - function instantiateTopOfMultiple( - Machine.Hash _initialHash - ) external returns (RootTournament); -} diff --git a/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol deleted file mode 100644 index 759097921..000000000 --- a/onchain/permissionless-arbitration/src/tournament/interfaces/ISingleTournamentFactory.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0 <0.9; - -import "../abstracts/RootTournament.sol"; -import "../abstracts/NonRootTournament.sol"; - -interface ISingleTournamentFactory { - event rootCreated(RootTournament); - - function instantiateSingle( - Machine.Hash _initialHash - ) external returns (RootTournament); -} diff --git a/onchain/permissionless-arbitration/test/MultiTournament.t.sol b/onchain/permissionless-arbitration/test/MultiTournament.t.sol index 793ddd812..24bcae5d0 100644 --- a/onchain/permissionless-arbitration/test/MultiTournament.t.sol +++ b/onchain/permissionless-arbitration/test/MultiTournament.t.sol @@ -14,8 +14,7 @@ import "forge-std/console.sol"; import "forge-std/Test.sol"; import "./Util.sol"; -import "src/tournament/factories/RootTournamentFactory.sol"; -import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/tournament/factories/TournamentFactory.sol"; import "src/CanonicalConstants.sol"; pragma solidity ^0.8.0; @@ -30,8 +29,7 @@ contract MultiTournamentTest is Test { // player 0, player 1, and player 2 Tree.Node[][3] playerNodes; - IRootTournamentFactory immutable rootFactory; - IInnerTournamentFactory immutable innerFactory; + TournamentFactory immutable factory; TopTournament topTournament; MiddleTournament middleTournament; @@ -43,8 +41,7 @@ contract MultiTournamentTest is Test { event newInnerTournament(Match.IdHash indexed, NonRootTournament); constructor() { - innerFactory = new InnerTournamentFactory(); - rootFactory = new RootTournamentFactory(innerFactory); + factory = Util.instantiateTournamentFactory(); } function setUp() public { @@ -75,7 +72,7 @@ contract MultiTournamentTest is Test { function testRootWinner() public { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // no winner before tournament finished @@ -128,7 +125,7 @@ contract MultiTournamentTest is Test { function testInner() public { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // pair commitment, expect a match @@ -160,7 +157,7 @@ contract MultiTournamentTest is Test { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // pair commitment, expect a match @@ -194,7 +191,7 @@ contract MultiTournamentTest is Test { function testInnerWinner() public { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // pair commitment, expect a match @@ -278,7 +275,7 @@ contract MultiTournamentTest is Test { //create another tournament for other test topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // pair commitment, expect a match diff --git a/onchain/permissionless-arbitration/test/Tournament.t.sol b/onchain/permissionless-arbitration/test/Tournament.t.sol index 810ed22a4..98c4320a1 100644 --- a/onchain/permissionless-arbitration/test/Tournament.t.sol +++ b/onchain/permissionless-arbitration/test/Tournament.t.sol @@ -14,8 +14,7 @@ import "forge-std/console.sol"; import "forge-std/Test.sol"; import "./Util.sol"; -import "src/tournament/factories/RootTournamentFactory.sol"; -import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/tournament/factories/TournamentFactory.sol"; import "src/CanonicalConstants.sol"; pragma solidity ^0.8.0; @@ -31,8 +30,7 @@ contract TournamentTest is Test { Tree.Node[][3] playerNodes; Tree.Node constant ONE_NODE = Tree.Node.wrap(bytes32(uint256(1))); - IRootTournamentFactory immutable rootFactory; - IInnerTournamentFactory immutable innerFactory; + TournamentFactory immutable factory; TopTournament topTournament; MiddleTournament middleTournament; @@ -44,8 +42,7 @@ contract TournamentTest is Test { event newInnerTournament(Match.IdHash indexed, NonRootTournament); constructor() { - innerFactory = new InnerTournamentFactory(); - rootFactory = new RootTournamentFactory(innerFactory); + factory = Util.instantiateTournamentFactory(); } function setUp() public { @@ -76,7 +73,7 @@ contract TournamentTest is Test { function testJoinTournament() public { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); // duplicate commitment should be reverted @@ -97,7 +94,7 @@ contract TournamentTest is Test { function testTimeout() public { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); uint256 _t = block.timestamp; @@ -150,7 +147,7 @@ contract TournamentTest is Test { topTournament = Util.initializePlayer0Tournament( playerNodes, - rootFactory + factory ); _t = block.timestamp; diff --git a/onchain/permissionless-arbitration/test/TournamentFactory.t.sol b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol index 6ef904dd2..e2a0a3b26 100644 --- a/onchain/permissionless-arbitration/test/TournamentFactory.t.sol +++ b/onchain/permissionless-arbitration/test/TournamentFactory.t.sol @@ -14,25 +14,24 @@ import "forge-std/console.sol"; import "forge-std/Test.sol"; import "src/tournament/abstracts/RootTournament.sol"; -import "src/tournament/factories/RootTournamentFactory.sol"; -import "src/tournament/factories/InnerTournamentFactory.sol"; +import "src/tournament/factories/TournamentFactory.sol"; import "src/CanonicalConstants.sol"; +import "./Util.sol"; + pragma solidity ^0.8.0; contract TournamentFactoryTest is Test { - IRootTournamentFactory rootFactory; - IInnerTournamentFactory innerFactory; + TournamentFactory factory; function setUp() public { - innerFactory = new InnerTournamentFactory(); - rootFactory = new RootTournamentFactory(innerFactory); + factory = Util.instantiateTournamentFactory(); } function testRootTournament() public { - RootTournament rootTournament = rootFactory.instantiateSingle( + RootTournament rootTournament = RootTournament(address(factory.instantiateSingleLevel( Machine.ZERO_STATE - ); + ))); (uint64 _level, uint64 _log2step, uint64 _height) = rootTournament .tournamentLevelConstants(); @@ -49,9 +48,9 @@ contract TournamentFactoryTest is Test { "height should match" ); - rootTournament = rootFactory.instantiateTopOfMultiple( + rootTournament = RootTournament(address(factory.instantiateTop( Machine.ZERO_STATE - ); + ))); (_level, _log2step, _height) = rootTournament .tournamentLevelConstants(); diff --git a/onchain/permissionless-arbitration/test/Util.sol b/onchain/permissionless-arbitration/test/Util.sol index 2731414a9..2f887133a 100644 --- a/onchain/permissionless-arbitration/test/Util.sol +++ b/onchain/permissionless-arbitration/test/Util.sol @@ -14,7 +14,11 @@ import "src/Match.sol"; import "src/CanonicalConstants.sol"; import "src/tournament/concretes/TopTournament.sol"; import "src/tournament/concretes/MiddleTournament.sol"; -import "src/tournament/interfaces/IRootTournamentFactory.sol"; + +import "src/tournament/factories/SingleLevelTournamentFactory.sol"; +import "src/tournament/factories/TopTournamentFactory.sol"; +import "src/tournament/factories/MiddleTournamentFactory.sol"; +import "src/tournament/factories/BottomTournamentFactory.sol"; pragma solidity ^0.8.0; @@ -109,10 +113,10 @@ library Util { // create new _topTournament and player 0 joins it function initializePlayer0Tournament( Tree.Node[][3] memory _playerNodes, - IRootTournamentFactory _rootFactory + TournamentFactory _factory ) internal returns (TopTournament _topTournament) { _topTournament = TopTournament( - address(_rootFactory.instantiateTopOfMultiple(Machine.ZERO_STATE)) + address(_factory.instantiateTop(Machine.ZERO_STATE)) ); // player 0 joins tournament joinTopTournament(_playerNodes, _topTournament, 0); @@ -215,4 +219,15 @@ library Util { _playerNodes[_opponent][ArbitrationConstants.height(_level)] ); } + + + // instantiates all sub-factories and TournamentFactory + function instantiateTournamentFactory() internal returns (TournamentFactory) { + SingleLevelTournamentFactory singleLevelFactory = new SingleLevelTournamentFactory(); + TopTournamentFactory topFactory = new TopTournamentFactory(); + MiddleTournamentFactory middleFactory = new MiddleTournamentFactory(); + BottomTournamentFactory bottomFactory = new BottomTournamentFactory(); + + return new TournamentFactory(singleLevelFactory, topFactory, middleFactory, bottomFactory); + } } From 9b025c179c25a36ff53c734f38576b326bdcd5c5 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 3 Aug 2023 10:59:41 +0300 Subject: [PATCH 10/25] refactor(arbitration): use level instead of height in `Match.State` --- .../permissionless-arbitration/src/Match.sol | 33 +++++++++++-------- .../tournament/abstracts/LeafTournament.sol | 2 +- .../abstracts/NonLeafTournament.sol | 2 +- .../src/tournament/abstracts/Tournament.sol | 4 +-- .../test/Match.t.sol | 6 ++++ 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index a622f37f4..b9a470f46 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -61,8 +61,9 @@ library Match { // Once match is done, leftNode and rightNode change meaning // and contains contested final states. uint256 runningLeafPosition; - uint64 height; uint64 currentHeight; + + uint64 level; // constant } function createMatch( @@ -70,7 +71,7 @@ library Match { Tree.Node two, Tree.Node leftNodeOfTwo, Tree.Node rightNodeOfTwo, - uint64 height + uint64 level ) internal pure returns (IdHash, State memory) { assert(two.verify(leftNodeOfTwo, rightNodeOfTwo)); @@ -81,8 +82,8 @@ library Match { leftNodeOfTwo, rightNodeOfTwo, 0, - height, - height + ArbitrationConstants.height(level), + level ); return (matchId.hashFromId(), state); @@ -126,7 +127,7 @@ library Match { state.rightNode = leftLeaf; state.currentHeight = 0; - if (state.height % 2 == 0) { + if (state.height() % 2 == 0) { finalStateOne = state.leftNode.toMachineHash(); finalStateTwo = state.rightNode.toMachineHash(); } else { @@ -147,7 +148,7 @@ library Match { state.runningLeafPosition += 1; // TODO: verify state.currentHeight = 0; - if (state.height % 2 == 0) { + if (state.height() % 2 == 0) { finalStateOne = state.rightNode.toMachineHash(); finalStateTwo = state.leftNode.toMachineHash(); } else { @@ -158,8 +159,7 @@ library Match { function getDivergence( State storage state, - uint256 startCycle, - uint64 level + uint256 startCycle ) internal view @@ -172,11 +172,11 @@ library Match { { assert(state.currentHeight == 0); agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); - agreeCycle = state.toCycle(startCycle, level); + agreeCycle = state.toCycle(startCycle); if (state.runningLeafPosition % 2 == 0) { // divergence was set on left leaf - if (state.height % 2 == 0) { + if (state.height() % 2 == 0) { finalStateOne = state.leftNode.toMachineHash(); finalStateTwo = state.rightNode.toMachineHash(); } else { @@ -185,7 +185,7 @@ library Match { } } else { // divergence was set on right leaf - if (state.height % 2 == 0) { + if (state.height() % 2 == 0) { finalStateOne = state.rightNode.toMachineHash(); finalStateTwo = state.leftNode.toMachineHash(); } else { @@ -232,13 +232,18 @@ library Match { function toCycle( State memory state, - uint256 startCycle, - uint64 level + uint256 startCycle ) internal pure returns (uint256) { - uint64 log2step = ArbitrationConstants.log2step(level); + uint64 log2step = ArbitrationConstants.log2step(state.level); return _toCycle(state, startCycle, log2step); } + function height( + State memory state + ) internal pure returns (uint64) { + return ArbitrationConstants.height(state.level); + } + // // Requires // diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index 97b1030be..598085acf 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -85,7 +85,7 @@ abstract contract LeafTournament is Tournament { uint256 _agreeCycle, Machine.Hash _finalStateOne, Machine.Hash _finalStateTwo - ) = _matchState.getDivergence(startCycle, level); + ) = _matchState.getDivergence(startCycle); Machine.Hash _finalState = runMetaStep( _agreeHash, diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index b03edd76d..19031a836 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -105,7 +105,7 @@ abstract contract NonLeafTournament is Tournament { _matchId.commitmentTwo, _finalStateTwo, _maxDuration, - _matchState.toCycle(startCycle, level), + _matchState.toCycle(startCycle), level + 1 ); matchIdFromInnerTournaments[_inner] = _matchId.hashFromId(); diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index 50b22045d..24ebacbca 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -263,7 +263,7 @@ abstract contract Tournament { Match.IdHash _matchIdHash ) external view returns (uint256) { Match.State memory _m = getMatch(_matchIdHash); - return _m.toCycle(startCycle, level); + return _m.toCycle(startCycle); } function tournamentLevelConstants() @@ -329,7 +329,7 @@ abstract contract Tournament { _rootHash, _leftNode, _rightNode, - ArbitrationConstants.height(level) + level ); matches[_matchId] = _matchState; diff --git a/onchain/permissionless-arbitration/test/Match.t.sol b/onchain/permissionless-arbitration/test/Match.t.sol index 49ce2287e..9cea84e76 100644 --- a/onchain/permissionless-arbitration/test/Match.t.sol +++ b/onchain/permissionless-arbitration/test/Match.t.sol @@ -18,6 +18,11 @@ import "src/CanonicalConstants.sol"; pragma solidity ^0.8.0; +// TODO: we cannot set the height of a match anymore +// To properly test, we'll need to swap the implementation of +// ArbitrationConstants. + +/* contract MatchTest is Test { using Tree for Tree.Node; using Machine for Machine.Hash; @@ -160,3 +165,4 @@ contract MatchTest is Test { ); } } +*/ From be9ab47b120dc6bc91be9abbc5dbca784218aada Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Mon, 7 Aug 2023 11:18:20 +0200 Subject: [PATCH 11/25] fix(arbitration): fix match advance check Remove unnecessary imports. --- onchain/permissionless-arbitration/src/CanonicalConstants.sol | 2 +- onchain/permissionless-arbitration/src/Match.sol | 2 +- .../src/tournament/factories/TournamentFactory.sol | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol index 300dce0bd..4d764ce9b 100644 --- a/onchain/permissionless-arbitration/src/CanonicalConstants.sol +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -24,7 +24,7 @@ library ArbitrationConstants { // 4-level tournament uint64 constant LEVELS = 4; - uint64 constant LOG2_MAX_MCYCLE = 63; + // uint64 constant LOG2_MAX_MCYCLE = 63; /// @return log2step gap of each leaf in the tournament[level] function log2step(uint64 level) internal pure returns (uint64) { diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index b9a470f46..b8c2e75dd 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -261,7 +261,7 @@ library Match { } function requireCanBeAdvanced(State memory state) internal pure { - require(!state.canBeFinalized(), "match is ready to be finalized"); + require(state.canBeAdvanced(), "match can't be advanced"); } function requireParentHasChildren( diff --git a/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol index c483d384d..30c98c8aa 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; -import "../abstracts/NonRootTournament.sol"; import "../concretes/TopTournament.sol"; import "../concretes/MiddleTournament.sol"; import "../concretes/BottomTournament.sol"; From 6fb7b5c49a0e1dd8d93610a6f08623dbfa94b0c3 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 10 Aug 2023 08:53:17 +0200 Subject: [PATCH 12/25] chore(arbitration): add license do sol files --- onchain/permissionless-arbitration/src/CanonicalConstants.sol | 4 +++- onchain/permissionless-arbitration/src/Clock.sol | 4 +++- onchain/permissionless-arbitration/src/Commitment.sol | 4 +++- onchain/permissionless-arbitration/src/Machine.sol | 4 +++- onchain/permissionless-arbitration/src/Match.sol | 4 +++- onchain/permissionless-arbitration/src/Merkle.sol | 4 +++- onchain/permissionless-arbitration/src/Time.sol | 4 +++- onchain/permissionless-arbitration/src/Tree.sol | 4 +++- .../src/tournament/abstracts/LeafTournament.sol | 4 +++- .../src/tournament/abstracts/NonLeafTournament.sol | 4 +++- .../src/tournament/abstracts/NonRootTournament.sol | 4 +++- .../src/tournament/abstracts/RootTournament.sol | 4 +++- .../src/tournament/abstracts/Tournament.sol | 4 +++- .../src/tournament/concretes/BottomTournament.sol | 4 +++- .../src/tournament/concretes/MiddleTournament.sol | 4 +++- .../src/tournament/concretes/SingleLevelTournament.sol | 4 +++- .../src/tournament/concretes/TopTournament.sol | 4 +++- .../src/tournament/factories/BottomTournamentFactory.sol | 4 +++- .../src/tournament/factories/ITournamentFactory.sol | 4 +++- .../src/tournament/factories/MiddleTournamentFactory.sol | 4 +++- .../src/tournament/factories/SingleLevelTournamentFactory.sol | 4 +++- .../src/tournament/factories/TopTournamentFactory.sol | 4 +++- .../src/tournament/factories/TournamentFactory.sol | 4 +++- 23 files changed, 69 insertions(+), 23 deletions(-) diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol index 4d764ce9b..7472cd62b 100644 --- a/onchain/permissionless-arbitration/src/CanonicalConstants.sol +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Time.sol"; diff --git a/onchain/permissionless-arbitration/src/Clock.sol b/onchain/permissionless-arbitration/src/Clock.sol index 67a9ecd2c..0eef80c15 100644 --- a/onchain/permissionless-arbitration/src/Clock.sol +++ b/onchain/permissionless-arbitration/src/Clock.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Time.sol"; diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index b3804b555..58966b1ff 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./CanonicalConstants.sol"; diff --git a/onchain/permissionless-arbitration/src/Machine.sol b/onchain/permissionless-arbitration/src/Machine.sol index fde20e0f4..f6a97758e 100644 --- a/onchain/permissionless-arbitration/src/Machine.sol +++ b/onchain/permissionless-arbitration/src/Machine.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; library Machine { diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index b8c2e75dd..b80eec616 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./CanonicalConstants.sol"; diff --git a/onchain/permissionless-arbitration/src/Merkle.sol b/onchain/permissionless-arbitration/src/Merkle.sol index e5e309fbc..1cfd1a813 100644 --- a/onchain/permissionless-arbitration/src/Merkle.sol +++ b/onchain/permissionless-arbitration/src/Merkle.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; library Merkle { diff --git a/onchain/permissionless-arbitration/src/Time.sol b/onchain/permissionless-arbitration/src/Time.sol index 5ef924d20..40cd1ae34 100644 --- a/onchain/permissionless-arbitration/src/Time.sol +++ b/onchain/permissionless-arbitration/src/Time.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; library Time { diff --git a/onchain/permissionless-arbitration/src/Tree.sol b/onchain/permissionless-arbitration/src/Tree.sol index e56c49fda..5bfdfc6d9 100644 --- a/onchain/permissionless-arbitration/src/Tree.sol +++ b/onchain/permissionless-arbitration/src/Tree.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Machine.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index 598085acf..8212535ea 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Tournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index 19031a836..282583bbd 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../factories/ITournamentFactory.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol index c8ef240ce..45d1747e6 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Tournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol index ed1c78269..0b0836ce7 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./Tournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index 24ebacbca..3e4d7af1d 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../../CanonicalConstants.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol index b702c9396..098e23c98 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/BottomTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../abstracts/LeafTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol index 1accf9c1d..c8861fff5 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/MiddleTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../abstracts/NonLeafTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol index e7e7124b6..3a2a20dea 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/SingleLevelTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../abstracts/RootTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol index da9368f05..263bea69e 100644 --- a/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/concretes/TopTournament.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../abstracts/RootTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol index 290a77ad9..c164907a3 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/BottomTournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../concretes/BottomTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol index eca00ca4e..ad243d07a 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/ITournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../abstracts/Tournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol index 7ed94beec..409c0c2d7 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/MiddleTournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "./TournamentFactory.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol index ccdee5678..5ea95c4c5 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/SingleLevelTournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../concretes/SingleLevelTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol index 5c0999ac0..2b4f5e04f 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/TopTournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../concretes/TopTournament.sol"; diff --git a/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol index 30c98c8aa..fe3e61797 100644 --- a/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol +++ b/onchain/permissionless-arbitration/src/tournament/factories/TournamentFactory.sol @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: UNLICENSED +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + pragma solidity ^0.8.17; import "../concretes/TopTournament.sol"; From 5860a9bfb11a5b6e8e61ae06ee9e25ec086fff70 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 10 Aug 2023 14:27:51 +0200 Subject: [PATCH 13/25] refactor(arbitration): improve solidity code quality --- .../src/Commitment.sol | 77 ++++- .../permissionless-arbitration/src/Match.sol | 266 +++++++++++------- .../tournament/abstracts/LeafTournament.sol | 50 ++-- .../abstracts/NonLeafTournament.sol | 40 +-- .../src/tournament/abstracts/Tournament.sol | 26 +- 5 files changed, 278 insertions(+), 181 deletions(-) diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index 58966b1ff..8dede2d09 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -10,27 +10,84 @@ import "./Machine.sol"; // import "./Merkle.sol"; library Commitment { + using Tree for Tree.Node; using Commitment for Tree.Node; - function proveFinalState( - Tree.Node root, + function requireState( + Tree.Node commitment, uint64 level, - Machine.Hash finalState, + uint256 position, + Machine.Hash state, bytes32[] calldata hashProof ) internal pure { - root.proveHash( - uint64(1 << ArbitrationConstants.height(level)), - finalState, + uint64 treeHeight = ArbitrationConstants.height(level); + Tree.Node expectedCommitment = getRoot( + Machine.Hash.unwrap(state), + treeHeight, + position, hashProof ); + + require(commitment.eq(expectedCommitment), "commitment state doesn't match"); + } + + + function isEven(uint256 x) private pure returns (bool) { + return x % 2 == 0; } - function proveHash( - Tree.Node root, + function getRoot( + bytes32 leaf, + uint64 treeHeight, uint256 position, - Machine.Hash hash, + bytes32[] calldata siblings + ) internal pure returns (Tree.Node) { + uint nodesCount = treeHeight - 1; + assert(nodesCount == siblings.length); + + for (uint i = 0; i < nodesCount; i++) { + if (isEven(position >> i)) { + leaf = + keccak256(abi.encodePacked(leaf, siblings[i])); + } else { + leaf = + keccak256(abi.encodePacked(siblings[i], leaf)); + } + } + + return Tree.Node.wrap(leaf); + } + + + function requireFinalState( + Tree.Node commitment, + uint64 level, + Machine.Hash finalState, bytes32[] calldata hashProof ) internal pure { - // TODO: call Merkle library + uint64 treeHeight = ArbitrationConstants.height(level); + Tree.Node expectedCommitment = getRootForLastLeaf( + treeHeight, + Machine.Hash.unwrap(finalState), + hashProof + ); + + require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); + } + + + function getRootForLastLeaf( + uint64 treeHeight, + bytes32 leaf, + bytes32[] calldata siblings + ) internal pure returns (Tree.Node) { + uint nodesCount = treeHeight - 1; + assert(nodesCount == siblings.length); + + for (uint i = 0; i < nodesCount; i++) { + leaf = keccak256(abi.encodePacked(siblings[i], leaf)); + } + + return Tree.Node.wrap(leaf); } } diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index b80eec616..09a19b108 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.17; import "./CanonicalConstants.sol"; import "./Tree.sol"; import "./Machine.sol"; +import "./Commitment.sol"; /// @notice Implements functionalities to advance a match, until the point where divergence is found. library Match { @@ -13,6 +14,15 @@ library Match { using Match for Id; using Match for IdHash; using Match for State; + using Machine for Machine.Hash; + using Commitment for Tree.Node; + + // + // Events + // + + event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left); + // // Id @@ -91,120 +101,72 @@ library Match { return (matchId.hashFromId(), state); } - function goDownLeftTree( - State storage state, - Tree.Node newLeftNode, - Tree.Node newRightNode - ) internal { - assert(state.currentHeight > 1); - state.otherParent = state.leftNode; - state.leftNode = newLeftNode; - state.rightNode = newRightNode; - - state.currentHeight--; - } - - function goDownRightTree( + function advanceMatch( State storage state, + Id calldata id, + Tree.Node leftNode, + Tree.Node rightNode, Tree.Node newLeftNode, Tree.Node newRightNode ) internal { - assert(state.currentHeight > 1); - state.otherParent = state.rightNode; - state.leftNode = newLeftNode; - state.rightNode = newRightNode; - - state.runningLeafPosition += 1 << state.currentHeight; // TODO: verify - state.currentHeight--; - } - - function setDivergenceOnLeftLeaf( - State storage state, - Tree.Node leftLeaf - ) - internal - returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) - { - assert(state.currentHeight == 1); - state.rightNode = leftLeaf; - state.currentHeight = 0; - - if (state.height() % 2 == 0) { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); + if (!state.agreesOnLeftNode(leftNode)) { + // go down left in Commitment tree + leftNode.requireChildren(newLeftNode, newRightNode); + state._goDownLeftTree(newLeftNode, newRightNode); } else { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); + // go down right in Commitment tree + rightNode.requireChildren(newLeftNode, newRightNode); + state._goDownRightTree(newLeftNode, newRightNode); } - } - - function setDivergenceOnRightLeaf( - State storage state, - Tree.Node rightLeaf - ) - internal - returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) - { - assert(state.currentHeight == 1); - state.leftNode = rightLeaf; - state.runningLeafPosition += 1; // TODO: verify - state.currentHeight = 0; - if (state.height() % 2 == 0) { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } else { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } + emit matchAdvanced( + id.hashFromId(), + state.otherParent, + state.leftNode + ); } - function getDivergence( + function sealMatch( State storage state, - uint256 startCycle + Id calldata id, + Machine.Hash initialState, + Tree.Node leftLeaf, + Tree.Node rightLeaf, + Machine.Hash agreeState, + bytes32[] calldata agreeStateProof ) internal - view returns ( - Machine.Hash agreeHash, - uint256 agreeCycle, - Machine.Hash finalStateOne, - Machine.Hash finalStateTwo + Machine.Hash divergentStateOne, + Machine.Hash divergentStateTwo ) { - assert(state.currentHeight == 0); - agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); - agreeCycle = state.toCycle(startCycle); + if (!state.agreesOnLeftNode(leftLeaf)) { + // Divergence is in the left leaf! + (divergentStateOne, divergentStateTwo) = state + ._setDivergenceOnLeftLeaf(leftLeaf); + } else { + // Divergence is in the right leaf! + (divergentStateOne, divergentStateTwo) = state + ._setDivergenceOnRightLeaf(rightLeaf); + } - if (state.runningLeafPosition % 2 == 0) { - // divergence was set on left leaf - if (state.height() % 2 == 0) { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } else { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } + // Prove initial hash is in commitment + if (state.runningLeafPosition == 0) { + require(agreeState.eq(initialState), "agree hash incorrect"); } else { - // divergence was set on right leaf - if (state.height() % 2 == 0) { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } else { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } + id.commitmentOne.requireState( + state.level, + state.runningLeafPosition - 1, + agreeState, + agreeStateProof + ); } - } - function setInitialState( - State storage state, - Machine.Hash initialState - ) internal { - assert(state.currentHeight == 0); - state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); + state._setAgreeState(agreeState); } + // // View methods // @@ -246,6 +208,45 @@ library Match { return ArbitrationConstants.height(state.level); } + function getDivergence( + State memory state, + uint256 startCycle + ) + internal + pure + returns ( + Machine.Hash agreeHash, + uint256 agreeCycle, + Machine.Hash finalStateOne, + Machine.Hash finalStateTwo + ) + { + assert(state.currentHeight == 0); + agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); + agreeCycle = state.toCycle(startCycle); + + if (state.runningLeafPosition % 2 == 0) { + // divergence was set on left leaf + if (state.height() % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } else { + // divergence was set on right leaf + if (state.height() % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + } + + // // Requires // @@ -274,10 +275,87 @@ library Match { state.otherParent.requireChildren(leftNode, rightNode); } + // // Private // + function _goDownLeftTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.leftNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.currentHeight--; + } + + function _goDownRightTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.rightNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.runningLeafPosition += 1 << state.currentHeight; + state.currentHeight--; + } + + function _setDivergenceOnLeftLeaf( + State storage state, + Tree.Node leftLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.rightNode = leftLeaf; + state.currentHeight = 0; + + if (state.height() % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } + + function _setDivergenceOnRightLeaf( + State storage state, + Tree.Node rightLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.leftNode = rightLeaf; + state.runningLeafPosition += 1; + state.currentHeight = 0; + + if (state.height() % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + + function _setAgreeState( + State storage state, + Machine.Hash initialState + ) internal { + assert(state.currentHeight == 0); + state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); + } + function _toCycle( State memory state, uint256 base, @@ -285,6 +363,6 @@ library Match { ) internal pure returns (uint256) { uint256 step = 1 << log2step; uint256 leafPosition = state.runningLeafPosition; - return base + (leafPosition * step); // TODO verify + return base + (leafPosition * step); } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index 8212535ea..c336ef889 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.17; import "./Tournament.sol"; import "../../Commitment.sol"; -import "../../Merkle.sol"; import "step/ready_src/UArchStep.sol"; @@ -24,47 +23,32 @@ abstract contract LeafTournament is Tournament { Match.Id calldata _matchId, Tree.Node _leftLeaf, Tree.Node _rightLeaf, - Machine.Hash _initialHash, - bytes32[] calldata _initialHashProof + Machine.Hash _agreeHash, + bytes32[] calldata _agreeHashProof ) external tournamentNotFinished { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireExist(); _matchState.requireCanBeFinalized(); _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); - Machine.Hash _finalStateOne; - Machine.Hash _finalStateTwo; - - if (!_matchState.agreesOnLeftNode(_leftLeaf)) { - // Divergence is in the left leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnLeftLeaf(_leftLeaf); - } else { - // Divergence is in the right leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnRightLeaf(_rightLeaf); - } - // Unpause clocks - Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; - Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; - _clock1.setPaused(); - _clock1.advanceClock(); - _clock2.setPaused(); - _clock2.advanceClock(); - - // Prove initial hash is in commitment - if (_matchState.runningLeafPosition == 0) { - require(_initialHash.eq(initialHash), "initial hash incorrect"); - } else { - _matchId.commitmentOne.proveHash( - _matchState.runningLeafPosition, - _initialHash, - _initialHashProof - ); + { + Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; + Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; + _clock1.setPaused(); + _clock1.advanceClock(); + _clock2.setPaused(); + _clock2.advanceClock(); } - _matchState.setInitialState(_initialHash); + _matchState.sealMatch( + _matchId, + initialHash, + _leftLeaf, + _rightLeaf, + _agreeHash, + _agreeHashProof + ); } function winLeafMatch( diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index 282583bbd..b5b59c0cb 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -59,26 +59,13 @@ abstract contract NonLeafTournament is Tournament { Match.Id calldata _matchId, Tree.Node _leftLeaf, Tree.Node _rightLeaf, - Machine.Hash _initialHash, - bytes32[] calldata _initialHashProof + Machine.Hash _agreeHash, + bytes32[] calldata _agreeHashProof ) external tournamentNotFinished { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireCanBeFinalized(); _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); - Machine.Hash _finalStateOne; - Machine.Hash _finalStateTwo; - - if (!_matchState.agreesOnLeftNode(_leftLeaf)) { - // Divergence is in the left leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnLeftLeaf(_leftLeaf); - } else { - // Divergence is in the right leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnRightLeaf(_rightLeaf); - } - // Pause clocks Time.Duration _maxDuration; { @@ -89,19 +76,20 @@ abstract contract NonLeafTournament is Tournament { _maxDuration = Clock.max(_clock1, _clock2); } - // Prove initial hash is in commitment - if (_matchState.runningLeafPosition == 0) { - require(_initialHash.eq(initialHash), "initial hash incorrect"); - } else { - _matchId.commitmentOne.proveHash( - _matchState.runningLeafPosition - 1, - _initialHash, - _initialHashProof - ); - } + ( + Machine.Hash _finalStateOne, + Machine.Hash _finalStateTwo + ) = _matchState.sealMatch( + _matchId, + initialHash, + _leftLeaf, + _rightLeaf, + _agreeHash, + _agreeHashProof + ); NonRootTournament _inner = instantiateInner( - _initialHash, + _agreeHash, _matchId.commitmentOne, _finalStateOne, _matchId.commitmentTwo, diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index 3e4d7af1d..d4ca1bf4b 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -68,7 +68,6 @@ abstract contract Tournament { Tree.Node leftOfTwo ); - event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left); // // Modifiers @@ -125,7 +124,7 @@ abstract contract Tournament { Tree.Node _commitmentRoot = _leftNode.join(_rightNode); // Prove final state is in commitmentRoot - _commitmentRoot.proveFinalState(level, _finalState, _proof); + _commitmentRoot.requireFinalState(level, _finalState, _proof); // Verify whether finalState is one of the two allowed of tournament if nested requireValidContestedFinalState(_finalState); @@ -152,26 +151,17 @@ abstract contract Tournament { _matchState.requireCanBeAdvanced(); _matchState.requireParentHasChildren(_leftNode, _rightNode); - if (!_matchState.agreesOnLeftNode(_leftNode)) { - // go down left in Commitment tree - _leftNode.requireChildren(_newLeftNode, _newRightNode); - _matchState.goDownLeftTree(_newLeftNode, _newRightNode); - } else { - // go down right in Commitment tree - _rightNode.requireChildren(_newLeftNode, _newRightNode); - _matchState.goDownRightTree(_newLeftNode, _newRightNode); - } + _matchState.advanceMatch( + _matchId, + _leftNode, + _rightNode, + _newLeftNode, + _newRightNode + ); // advance clocks clocks[_matchId.commitmentOne].advanceClock(); clocks[_matchId.commitmentTwo].advanceClock(); - - // TODO move event to lib? - emit matchAdvanced( - _matchId.hashFromId(), - _matchState.otherParent, - _matchState.leftNode - ); } function winMatchByTimeout( From dafb1c2cb50eacfe15ec98bb84add67266b18473 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 10 Aug 2023 15:12:00 +0200 Subject: [PATCH 14/25] chore(arbitration): setup prototype test --- .../offchain/blockchain/client.lua | 95 +++++++++---------- .../offchain/blockchain/node.lua | 15 +-- .../offchain/computation/commitment.lua | 35 ++++++- .../offchain/computation/fake_commitment.lua | 18 ++++ .../offchain/computation/machine.lua | 3 +- .../offchain/constants.lua | 8 +- .../offchain/entrypoint.lua | 61 +++++++++++- .../offchain/player.lua | 21 ++-- .../offchain/utils/arithmetic.lua | 24 +++-- .../offchain/utils/eth_ebi.lua | 31 ++++++ 10 files changed, 225 insertions(+), 86 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/eth_ebi.lua diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 8b4edba9b..3d50b8e6a 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -1,5 +1,5 @@ local Hash = require "cryptography.hash" -local utils = require "utils" +local eth_ebi = require "utils.eth_ebi" local function parse_topics(json) local _, _, topics = json:find( @@ -7,7 +7,7 @@ local function parse_topics(json) ) local t = {} - for k,_ in string.gmatch(topics, [["(0x%x+)"]]) do + for k, _ in string.gmatch(topics, [["(0x%x+)"]]) do table.insert(t, k) end @@ -19,7 +19,7 @@ local function parse_data(json, sig) [==["data":"(0x%x+)"]==] ) - local decoded_data = utils.decode_event_data(sig, data) + local decoded_data = eth_ebi.decode_event_data(sig, data) return decoded_data end @@ -48,11 +48,11 @@ end local function parse_logs(logs, data_sig) local ret = {} - for k,_ in string.gmatch(logs, [[{[^}]*}]]) do + for k, _ in string.gmatch(logs, [[{[^}]*}]]) do local emited_topics = parse_topics(k) local decoded_data = parse_data(k, data_sig) local meta = parse_meta(k) - table.insert(ret, {emited_topics = emited_topics, decoded_data = decoded_data, meta = meta}) + table.insert(ret, { emited_topics = emited_topics, decoded_data = decoded_data, meta = meta }) end return ret @@ -62,7 +62,7 @@ local function join_tables(...) local function join(ret, t, ...) if not t then return ret end - for k,v in ipairs(t) do + for k, v in ipairs(t) do ret[k] = v end @@ -91,15 +91,15 @@ local function sort_and_dedup(t) end) local ret = {} - for k,v in ipairs(t) do - local v2 = t[k+1] + for k, v in ipairs(t) do + local v2 = t[k + 1] if not v2 then table.insert(ret, v) else local m1, m2 = v.meta, v2.meta if not (m1.block_number == m2.block_number and m1.log_index == m2.log_index) then table.insert(ret, v) - end + end end end @@ -107,8 +107,8 @@ local function sort_and_dedup(t) end local function quote_args(args, not_quote) - local quoted_args = {} - for _,v in ipairs(args) do + local quoted_args = {} + for _, v in ipairs(args) do if type(v) == "table" then if v._tag == "tuple" then local qa = quote_args(v, true) @@ -116,7 +116,7 @@ local function quote_args(args, not_quote) local sb = "'(" .. ca .. ")'" table.insert(quoted_args, sb) else - local qa = quote_args(v, true) + local qa = quote_args(v, true) local ca = table.concat(qa, ",") local sb = "'[" .. ca .. "]'" table.insert(quoted_args, sb) @@ -124,7 +124,7 @@ local function quote_args(args, not_quote) elseif not_quote then table.insert(quoted_args, v) else - table.insert(quoted_args, '"'..v..'"') + table.insert(quoted_args, '"' .. v .. '"') end end @@ -144,7 +144,6 @@ function Client:new(blockchain) setmetatable(client, self) return client - end local cast_logs_template = [==[ @@ -153,16 +152,16 @@ cast rpc -r "%s" eth_getLogs \ ]==] function Client:_read_logs(tournament_address, sig, topics, data_sig) - topics = topics or {false, false, false} - local encoded_sig = utils.encode_sig(sig) + topics = topics or { false, false, false } + local encoded_sig = eth_ebi.encode_sig(sig) table.insert(topics, 1, encoded_sig) assert(#topics == 4, "topics doesn't have four elements") local topics_strs = {} - for _,v in ipairs(topics) do + for _, v in ipairs(topics) do local s if v then - s = '"'..v..'"' + s = '"' .. v .. '"' else s = "null" end @@ -198,8 +197,8 @@ cast call --rpc-url "%s" "%s" "%s" %s 2>&1 function Client:_call(address, sig, args) local quoted_args = {} - for _,v in ipairs(args) do - table.insert(quoted_args, '"'..v..'"') + for _, v in ipairs(args) do + table.insert(quoted_args, '"' .. v .. '"') end local args_str = table.concat(quoted_args, " ") @@ -236,20 +235,20 @@ function Client:read_match_created(tournament_address, commitment_hash) local sig = "matchCreated(bytes32,bytes32,bytes32)" local data_sig = "(bytes32)" - local logs1 = self:_read_logs(tournament_address, sig, {commitment_hash, false, false}, data_sig) - local logs2 = self:_read_logs(tournament_address, sig, {false, commitment_hash, false}, data_sig) + local logs1 = self:_read_logs(tournament_address, sig, { commitment_hash, false, false }, data_sig) + local logs2 = self:_read_logs(tournament_address, sig, { false, commitment_hash, false }, data_sig) local logs = sort_and_dedup(join_tables(logs1, logs2)) local ret = {} - for k,v in ipairs(logs) do + for k, v in ipairs(logs) do local log = {} log.tournament_address = tournament_address log.meta = v.meta - log.commitment_one = Hash:from_digest(v.emited_topics[2]) - log.commitment_two = Hash:from_digest(v.emited_topics[3]) - log.left_hash = Hash:from_digest(v.decoded_data[1]) + log.commitment_one = Hash:from_digest_hex(v.emited_topics[2]) + log.commitment_two = Hash:from_digest_hex(v.emited_topics[3]) + log.left_hash = Hash:from_digest_hex(v.decoded_data[1]) log.match_id_hash = log.commitment_one:join(log.commitment_two) ret[k] = log @@ -261,7 +260,7 @@ end function Client:read_commitment(tournament_address, commitment_hash) local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" - local call_ret = self:_call(tournament_address, sig, {commitment_hash}) + local call_ret = self:_call(tournament_address, sig, { commitment_hash }) assert(#call_ret == 2) local allowance, last_resume = call_ret[1]:match "%((%d+),(%d+)%)" @@ -274,7 +273,7 @@ function Client:read_commitment(tournament_address, commitment_hash) local ret = { clock = clock, - final_state = Hash:from_digest(call_ret[2]) + final_state = Hash:from_digest_hex(call_ret[2]) } return ret @@ -284,14 +283,14 @@ function Client:read_tournament_created(tournament_address, match_id_hash) local sig = "newInnerTournament(bytes32,address)" local data_sig = "(address)" - local logs = self:_read_logs(tournament_address, sig, {match_id_hash, false, false}, data_sig) + local logs = self:_read_logs(tournament_address, sig, { match_id_hash, false, false }, data_sig) assert(#logs <= 1) if #logs == 0 then return false end local log = logs[1] local ret = { - parent_match = Hash:from_digest(match_id_hash), + parent_match = Hash:from_digest_hex(match_id_hash), new_tournament = log.decoded_data[1], } @@ -300,10 +299,10 @@ end function Client:match(address, match_id_hash) local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint64,uint64,uint64)" - local ret = self:_call(address, sig, {match_id_hash}) - ret[1] = Hash:from_digest(ret[1]) - ret[2] = Hash:from_digest(ret[2]) - ret[3] = Hash:from_digest(ret[3]) + local ret = self:_call(address, sig, { match_id_hash }) + ret[1] = Hash:from_digest_hex(ret[1]) + ret[2] = Hash:from_digest_hex(ret[2]) + ret[3] = Hash:from_digest_hex(ret[3]) return ret end @@ -312,18 +311,17 @@ function Client:tournament_winner(address) local sig = "tournamentWinner()(bytes32)" local ret = self:_call(address, sig, {}) - return Hash:from_digest(ret[1]) + return Hash:from_digest_hex(ret[1]) end function Client:root_tournament_winner(address) local sig = "rootTournamentFinalState()(bool,bytes32)" local ret = self:_call(address, sig, {}) - ret[2] = Hash:from_digest(ret[2]) + ret[2] = Hash:from_digest_hex(ret[2]) return ret end - function Client:maximum_delay(address) local sig = "maximumEnforceableDelay()(uint64)" local ret = self:_call(address, sig, {}) @@ -331,7 +329,6 @@ function Client:maximum_delay(address) return ret end - local cast_send_template = [[ cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 ]] @@ -363,7 +360,7 @@ end function Client:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] - self:_send_tx(tournament_address, sig, {final_state, proof, left_child, right_child}) + self:_send_tx(tournament_address, sig, { final_state, proof, left_child, right_child }) end function Client:tx_advance_match( @@ -373,30 +370,29 @@ function Client:tx_advance_match( self:_send_tx( tournament_address, sig, - {{commitment_one, commitment_two, _tag = "tuple"}, left, right, new_left, new_right} + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, new_left, new_right } ) end - function Client:tx_seal_inner_match( tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof ) local sig = - [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] self:_send_tx( tournament_address, sig, - {{commitment_one, commitment_two, _tag = "tuple"}, left, right, initial_hash, proof} + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } ) end function Client:tx_win_inner_match(tournament_address, child_tournament_address, left, right) local sig = - [[winInnerMatch(address,bytes32,bytes32)]] + [[winInnerMatch(address,bytes32,bytes32)]] self:_send_tx( tournament_address, sig, - {child_tournament_address, left, right} + { child_tournament_address, left, right } ) end @@ -404,11 +400,11 @@ function Client:tx_seal_leaf_match( tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof ) local sig = - [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] self:_send_tx( tournament_address, sig, - {{commitment_one, commitment_two, _tag = "tuple"}, left, right, initial_hash, proof} + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } ) end @@ -416,15 +412,14 @@ function Client:tx_win_leaf_match( tournament_address, commitment_one, commitment_two, left, right ) local sig = - [[winLeafMatch((bytes32,bytes32),bytes32,bytes32)]] + [[winLeafMatch((bytes32,bytes32),bytes32,bytes32)]] self:_send_tx( tournament_address, sig, - {{commitment_one, commitment_two, _tag = "tuple"}, left, right} + { { commitment_one, commitment_two, _tag = "tuple" }, left, right } ) end - return Client diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index dce2ea042..3ca56e131 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -18,10 +18,12 @@ local function start_blockchain(account_num) local pid = tonumber(reader:read()) - local handle = {reader = reader, pid = pid} - setmetatable(handle, {__gc = function(t) - stop_blockchain(t.reader, t.pid) - end}) + local handle = { reader = reader, pid = pid } + setmetatable(handle, { + __gc = function(t) + stop_blockchain(t.reader, t.pid) + end + }) print(string.format("Blockchain running with pid %d", pid)) return handle @@ -57,12 +59,11 @@ local function capture_blockchain_data(reader, account_num) _, _, endpoint = str:find("Listening on ([%w%p]+)") until endpoint - return {address = addresses, pk = pks}, endpoint + return { address = addresses, pk = pks }, endpoint end local function deploy_contracts(endpoint, deployer, initial_hash) - -- -- Deploy Single Level Factory print "Deploying Single Level factory..." @@ -183,7 +184,7 @@ function Blockchain:new(account_num) blockchain._handle = handle blockchain._accounts = accounts blockchain._current_account = 1 - blockchain.endpoint = "http://"..endpoint + blockchain.endpoint = "http://" .. endpoint setmetatable(blockchain, self) return blockchain diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua index 2f5781c18..e7280d169 100644 --- a/onchain/permissionless-arbitration/offchain/computation/commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -83,7 +83,10 @@ local function build_commitment(base_cycle, log2_stride, log2_stride_count, mach local machine = Machine:new_from_path(machine_path) if log2_stride >= consts.log2_uarch_span then - assert(log2_stride - consts.log2_uarch_span + log2_stride_count <= 63) + assert( + log2_stride + log2_stride_count <= + consts.log2_emulator_span + consts.log2_uarch_span + ) return build_big_machine_commitment(base_cycle, log2_stride, log2_stride_count, machine) else assert(log2_stride == 0) @@ -91,6 +94,34 @@ local function build_commitment(base_cycle, log2_stride, log2_stride_count, mach end end +local CommitmentBuilder = {} +CommitmentBuilder.__index = CommitmentBuilder + +function CommitmentBuilder:new(machine_path) + local c = { + machine_path = machine_path, + commitments = {} + } + setmetatable(c, self) + return c +end + +function CommitmentBuilder:build(base_cycle, level) + assert(level <= consts.levels) + if not self.commitments[level] then + self.commitments[level] = {} + elseif self.commitments[level][base_cycle] then + return self.commitments[level][base_cycle] + end + + local l = consts.levels - level + 1 + local log2_stride, log2_stride_count = consts.log2step[l], consts.heights[l] + print(log2_stride, log2_stride_count) + + local _, commitment = build_commitment(base_cycle, log2_stride, log2_stride_count, self.machine_path) + self.commitments[level][base_cycle] = commitment + return commitment +end -- local path = "program/simple-program" -- -- local initial, tree = build_commitment(0, 0, 64, path) @@ -154,4 +185,4 @@ x (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) (0 0 0 | x) -- end --]] -return build_commitment +return CommitmentBuilder diff --git a/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua new file mode 100644 index 000000000..c9f716dbf --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua @@ -0,0 +1,18 @@ +local Hash = require "cryptography.hash" +local consts = require "constants" + +local CommitmentBuilder = {} +CommitmentBuilder.__index = CommitmentBuilder + +function CommitmentBuilder:new() + local c = {} + setmetatable(c, self) + return c +end + +function CommitmentBuilder:build(_, level) + local commitment = Hash.zero:iterated_merkle(consts.heights[level]) + return commitment +end + +return CommitmentBuilder diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua index 1d9be3412..a52f7be5f 100644 --- a/onchain/permissionless-arbitration/offchain/computation/machine.lua +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -55,6 +55,7 @@ function Machine:new_from_path(path) cycle = 0, ucycle = 0, start_cycle = start_cycle, + initial_hash = Hash:from_digest(machine:get_root_hash()) } setmetatable(b, self) @@ -96,8 +97,6 @@ function Machine:increment_uarch() self.ucycle = self.ucycle + 1 end - - function Machine:ureset() assert(self.ucycle == arithmetic.max_uint64) self.machine:reset_uarch_state() diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua index 6cff43a61..a4d631b5c 100644 --- a/onchain/permissionless-arbitration/offchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -1,12 +1,12 @@ local arithmetic = require "utils.arithmetic" -local log2_uarch_span = 64 -local log2_emulator_span = 63 +local log2_uarch_span = 16 +local log2_emulator_span = 47 local constants = { levels = 4, - log2step = {24, 14, 7, 0}, - heights = {39, 10, 7, 7}, + log2step = { 24, 14, 7, 0 }, + heights = { 39, 10, 7, 7 }, log2_uarch_span = log2_uarch_span, uarch_span = arithmetic.max_uint(log2_uarch_span), diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 4ad7e2ec8..627534073 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -5,6 +5,59 @@ package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" +local machine_path = "offchain/program/simple-program" + +local Player = require "player" +local Client = require "blockchain.client" + +local Machine = require "computation.machine" +local initial_hash = Machine:new_from_path(machine_path):state().root_hash + +local Blockchain = require "blockchain.node" +local blockchain = Blockchain:new() +local contract = blockchain:deploy_contract(initial_hash) + + +local p1 +do + local CommitmentBuilder = require "computation.commitment" + local builder = CommitmentBuilder:new(machine_path) + local client = Client:new(blockchain) + p1 = Player:new(contract, client, builder) +end + +local p2 +do + local FakeCommitmentBuilder = require "computation.fake_commitment" + local builder = FakeCommitmentBuilder:new() + local client = Client:new(blockchain) + p2 = Player:new(contract, client, builder) +end + +local i = 0 +while true do + print(string.format("\n\n### ROUND %d ###\n", i)) + + print "Player 1 react" + if p1:react() then break end + + print "" + + print "Player 2 react" + if p2:react() then break end + + i = i + 1 +end + + + + + + + + + + -- os.execute "jsonrpc-remote-cartesi-machine --server-address=localhost:8080 &" -- os.execute "sleep 2" @@ -13,11 +66,11 @@ os.execute "cd offchain/program && ./gen_machine_simple.sh" -- require "computation.commitment" -- require "computation.machine_test" -local Blockchain = require "blockchain.node" +-- local Blockchain = require "blockchain.node" -local bc = Blockchain:new(100) -local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -bc:deploy_contract(initial_hash) +-- local bc = Blockchain:new(100) +-- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" +-- bc:deploy_contract(initial_hash) -- local utils = require "utils" diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua index 12abd061c..740fbad4b 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player.lua @@ -3,15 +3,16 @@ local constants = require "constants" local Player = {} Player.__index = Player -function Player:new(root_tournament_address, client, machine) +function Player:new(root_tournament_address, client, commitment_builder) local player = { root_tournament = { + base_big_cycle = 0, address = root_tournament_address, level = constants.levels, parent = false, }, client = client, - machine = machine, + commitment_builder = commitment_builder, commitments = {}, called_win = {} } @@ -28,11 +29,9 @@ end function Player:_react_tournament(tournament) local commitment = self.commitments[tournament.address] if not commitment then - commitment = self.machine:commitment( - constants.log2step[tournament.level], - constants.heights[constants.levels - tournament.level + 1], - false, -- TODO - false -- TODO + commitment = self.commitment_builder:build( + tournament.base_big_cycle, + tournament.level ) self.commitments[tournament.address] = commitment end @@ -114,7 +113,6 @@ function Player:_react_match(match, commitment) left, right ) - else local address = self.client:read_tournament_created( match.tournament.address, @@ -128,7 +126,6 @@ function Player:_react_match(match, commitment) return self:_react_tournament(new_tournament) end - elseif match.current_height == 1 then -- match to be sealed local found, left, right = commitment:children(match.current_other_parent) @@ -183,6 +180,7 @@ function Player:_react_match(match, commitment) new_tournament.address = address new_tournament.level = match.tournament.level - 1 new_tournament.parent = match.tournament + new_tournament.base_big_cycle = match.leaf_cycle return self:_react_tournament(new_tournament) end @@ -221,7 +219,6 @@ function Player:_react_match(match, commitment) end end - function Player:_latest_match(tournament, commitment) local matches = self.client:read_match_created(tournament.address, commitment.root) local last_match = matches[#matches] @@ -237,6 +234,10 @@ function Player:_latest_match(tournament, commitment) last_match.current_height = tonumber(m[6]) last_match.tournament = tournament + local level = tournament.level + last_match.leaf_cycle = tournament.base_big_cycle + + (last_match.running_leaf << (constants.log2step[level] + constants.log2_uarch_span)) + return last_match end diff --git a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua index 6192a0b26..18ce44c64 100644 --- a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua +++ b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua @@ -10,7 +10,7 @@ local function ulte(x, y) end local function is_pow2(x) - return (x & (x-1)) == 0 + return (x & (x - 1)) == 0 end -- Returns number of leading zeroes of x. Shamelessly stolen from the book @@ -18,12 +18,22 @@ end local function clz(x) if x == 0 then return 64 end local n = 0 - if (x & 0xFFFFFFFF00000000) == 0 then n = n + 32; x = x << 32 end - if (x & 0xFFFF000000000000) == 0 then n = n + 16; x = x << 16 end - if (x & 0xFF00000000000000) == 0 then n = n + 8; x = x << 8 end - if (x & 0xF000000000000000) == 0 then n = n + 4; x = x << 4 end - if (x & 0xC000000000000000) == 0 then n = n + 2; x = x << 2 end - if (x & 0x8000000000000000) == 0 then n = n + 1 end + if (x & 0xFFFFFFFF00000000) == 0 then + n = n + 32; x = x << 32 + end + if (x & 0xFFFF000000000000) == 0 then + n = n + 16; x = x << 16 + end + if (x & 0xFF00000000000000) == 0 then + n = n + 8; x = x << 8 + end + if (x & 0xF000000000000000) == 0 then + n = n + 4; x = x << 4 + end + if (x & 0xC000000000000000) == 0 then + n = n + 2; x = x << 2 + end + if (x & 0x8000000000000000) == 0 then n = n + 1 end return n end diff --git a/onchain/permissionless-arbitration/offchain/utils/eth_ebi.lua b/onchain/permissionless-arbitration/offchain/utils/eth_ebi.lua new file mode 100644 index 000000000..467bf8041 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/eth_ebi.lua @@ -0,0 +1,31 @@ +local function encode_sig(sig) + local cmd = string.format([[cast sig-event "%s"]], sig) + + local handle = io.popen(cmd) + assert(handle) + + local encoded_sig = handle:read() + handle:close() + return encoded_sig +end + +local function decode_event_data(sig, data) + local cmd = string.format([[cast --abi-decode "bananas()%s" %s]], sig, data) + + local handle = io.popen(cmd) + assert(handle) + + local decoded_data + local ret = {} + repeat + decoded_data = handle:read() + table.insert(ret, decoded_data) + until not decoded_data + handle:close() + return ret +end + +return { + encode_sig = encode_sig, + decode_event_data = decode_event_data, +} From 487e68dfc762dec345447dde892ee64a7012bbe1 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Fri, 18 Aug 2023 15:33:01 +0200 Subject: [PATCH 15/25] chore(arbitration): update node for new contracts --- .../offchain/blockchain/client.lua | 31 +- .../offchain/computation/commitment.lua | 6 +- .../offchain/computation/fake_commitment.lua | 11 +- .../offchain/computation/machine.lua | 2 +- .../offchain/constants.lua | 6 +- .../offchain/cryptography/hash.lua | 20 +- .../offchain/cryptography/merkle_builder.lua | 13 +- .../offchain/cryptography/merkle_tree.lua | 73 +- .../offchain/entrypoint.lua | 2 +- .../offchain/player.lua | 70 +- .../offchain/utils/bint.lua | 1756 +++++++++++++++++ .../src/CanonicalConstants.sol | 18 +- .../src/Commitment.sol | 12 +- 13 files changed, 1934 insertions(+), 86 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/utils/bint.lua diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 3d50b8e6a..fa4c026e7 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -1,4 +1,5 @@ local Hash = require "cryptography.hash" +local MerkleTree = require "cryptography.merkle_tree" local eth_ebi = require "utils.eth_ebi" local function parse_topics(json) @@ -109,7 +110,13 @@ end local function quote_args(args, not_quote) local quoted_args = {} for _, v in ipairs(args) do - if type(v) == "table" then + if type(v) == "table" and (getmetatable(v) == Hash or getmetatable(v) == MerkleTree) then + if not_quote then + table.insert(quoted_args, v:hex_string()) + else + table.insert(quoted_args, '"' .. v:hex_string() .. '"') + end + elseif type(v) == "table" then if v._tag == "tuple" then local qa = quote_args(v, true) local ca = table.concat(qa, ",") @@ -122,7 +129,7 @@ local function quote_args(args, not_quote) table.insert(quoted_args, sb) end elseif not_quote then - table.insert(quoted_args, v) + table.insert(quoted_args, tostring(v)) else table.insert(quoted_args, '"' .. v .. '"') end @@ -235,8 +242,8 @@ function Client:read_match_created(tournament_address, commitment_hash) local sig = "matchCreated(bytes32,bytes32,bytes32)" local data_sig = "(bytes32)" - local logs1 = self:_read_logs(tournament_address, sig, { commitment_hash, false, false }, data_sig) - local logs2 = self:_read_logs(tournament_address, sig, { false, commitment_hash, false }, data_sig) + local logs1 = self:_read_logs(tournament_address, sig, { commitment_hash:hex_string(), false, false }, data_sig) + local logs2 = self:_read_logs(tournament_address, sig, { false, commitment_hash:hex_string(), false }, data_sig) local logs = sort_and_dedup(join_tables(logs1, logs2)) @@ -260,7 +267,7 @@ end function Client:read_commitment(tournament_address, commitment_hash) local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" - local call_ret = self:_call(tournament_address, sig, { commitment_hash }) + local call_ret = self:_call(tournament_address, sig, { commitment_hash:hex_string() }) assert(#call_ret == 2) local allowance, last_resume = call_ret[1]:match "%((%d+),(%d+)%)" @@ -283,14 +290,14 @@ function Client:read_tournament_created(tournament_address, match_id_hash) local sig = "newInnerTournament(bytes32,address)" local data_sig = "(address)" - local logs = self:_read_logs(tournament_address, sig, { match_id_hash, false, false }, data_sig) + local logs = self:_read_logs(tournament_address, sig, { match_id_hash:hex_string(), false, false }, data_sig) assert(#logs <= 1) if #logs == 0 then return false end local log = logs[1] local ret = { - parent_match = Hash:from_digest_hex(match_id_hash), + parent_match = match_id_hash, new_tournament = log.decoded_data[1], } @@ -298,8 +305,8 @@ function Client:read_tournament_created(tournament_address, match_id_hash) end function Client:match(address, match_id_hash) - local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint64,uint64,uint64)" - local ret = self:_call(address, sig, { match_id_hash }) + local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint256,uint64,uint64)" + local ret = self:_call(address, sig, { match_id_hash:hex_string() }) ret[1] = Hash:from_digest_hex(ret[1]) ret[2] = Hash:from_digest_hex(ret[2]) ret[3] = Hash:from_digest_hex(ret[3]) @@ -382,7 +389,7 @@ function Client:tx_seal_inner_match( self:_send_tx( tournament_address, sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash:hex_string(), proof } ) end @@ -409,14 +416,14 @@ function Client:tx_seal_leaf_match( end function Client:tx_win_leaf_match( - tournament_address, commitment_one, commitment_two, left, right + tournament_address, commitment_one, commitment_two, left, right, proof ) local sig = [[winLeafMatch((bytes32,bytes32),bytes32,bytes32)]] self:_send_tx( tournament_address, sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right } + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, proof } ) end diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua index e7280d169..bb96b100c 100644 --- a/onchain/permissionless-arbitration/offchain/computation/commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -50,7 +50,7 @@ local function build_small_machine_commitment(base_cycle, log2_stride_count, mac end end - return initial_state, builder:build() + return initial_state, builder:build(initial_state) end @@ -76,12 +76,14 @@ local function build_big_machine_commitment(base_cycle, log2_stride, log2_stride end end - return initial_state, builder:build() + return initial_state, builder:build(initial_state) end local function build_commitment(base_cycle, log2_stride, log2_stride_count, machine_path) local machine = Machine:new_from_path(machine_path) + print(log2_stride, log2_stride_count) + if log2_stride >= consts.log2_uarch_span then assert( log2_stride + log2_stride_count <= diff --git a/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua index c9f716dbf..c1a443827 100644 --- a/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua @@ -1,18 +1,21 @@ +local MerkleBuilder = require "cryptography.merkle_builder" local Hash = require "cryptography.hash" local consts = require "constants" local CommitmentBuilder = {} CommitmentBuilder.__index = CommitmentBuilder -function CommitmentBuilder:new() - local c = {} +function CommitmentBuilder:new(initial_hash) + local c = { initial_hash = initial_hash } setmetatable(c, self) return c end function CommitmentBuilder:build(_, level) - local commitment = Hash.zero:iterated_merkle(consts.heights[level]) - return commitment + local builder = MerkleBuilder:new() + builder:add(Hash.zero, 1 << consts.heights[consts.levels - level + 1]) + -- local commitment = Hash.zero:iterated_merkle(consts.heights[level]) + return builder:build(self.initial_hash) end return CommitmentBuilder diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua index a52f7be5f..afaff5835 100644 --- a/onchain/permissionless-arbitration/offchain/computation/machine.lua +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -1,5 +1,6 @@ local Hash = require "cryptography.hash" local arithmetic = require "utils.arithmetic" +local consts = require "constants" local cartesi = require "cartesi" local ComputationState = {} @@ -98,7 +99,6 @@ function Machine:increment_uarch() end function Machine:ureset() - assert(self.ucycle == arithmetic.max_uint64) self.machine:reset_uarch_state() self.cycle = self.cycle + 1 self.ucycle = 0 diff --git a/onchain/permissionless-arbitration/offchain/constants.lua b/onchain/permissionless-arbitration/offchain/constants.lua index a4d631b5c..a4a6bd695 100644 --- a/onchain/permissionless-arbitration/offchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/constants.lua @@ -4,9 +4,9 @@ local log2_uarch_span = 16 local log2_emulator_span = 47 local constants = { - levels = 4, - log2step = { 24, 14, 7, 0 }, - heights = { 39, 10, 7, 7 }, + levels = 3, + log2step = { 31, 16, 0 }, + heights = { 32, 15, 16 }, log2_uarch_span = log2_uarch_span, uarch_span = arithmetic.max_uint(log2_uarch_span), diff --git a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua index 14663ea52..74b4dd099 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua @@ -2,7 +2,7 @@ local keccak = require "cartesi".keccak local function hex_from_bin(bin) assert(bin:len() == 32) - return "0x" .. (bin:gsub('.', function (c) + return "0x" .. (bin:gsub('.', function(c) return string.format('%02x', string.byte(c)) end)) end @@ -10,7 +10,7 @@ end local function bin_from_hex(hex) assert(hex:len() == 66, string.format("%s %d", hex, hex:len())) local h = assert(hex:match("0x(%x+)"), hex) - return (h:gsub('..', function (cc) + return (h:gsub('..', function(cc) return string.char(tonumber(cc, 16)) end)) end @@ -27,8 +27,8 @@ function Hash:from_digest(digest) local x = internalized_hahes[digest] if x then return x end - local h = {digest = digest} - iterateds[h] = {h} + local h = { digest = digest } + iterateds[h] = { h } setmetatable(h, self) internalized_hahes[digest] = h return h @@ -46,12 +46,12 @@ function Hash:from_data(data) end function Hash:join(other_hash) - Hash:is_of_type_hash(other_hash) + assert(Hash:is_of_type_hash(other_hash)) local digest = keccak(self.digest, other_hash.digest) local ret = Hash:from_digest(digest) - ret.left = self.digest - ret.right = other_hash.digest + ret.left = self + ret.right = other_hash return ret end @@ -82,7 +82,11 @@ function Hash:iterated_merkle(level) return highest_level end -Hash.__tostring = function (x) +function Hash:hex_string() + return hex_from_bin(self.digest) +end + +Hash.__tostring = function(x) return hex_from_bin(x.digest) end diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua index 152c4d7b3..b84ff60d6 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_builder.lua @@ -60,7 +60,6 @@ function Slice:find_cell_containing(elem) return l end - local MerkleBuilder = {} MerkleBuilder.__index = MerkleBuilder @@ -82,17 +81,17 @@ function MerkleBuilder:add(hash, rep) local accumulated_count = rep + last.accumulated_count if not math.ult(rep, accumulated_count) then -- overflow... - assert(accumulated_count == 0) -- then it has to be zero, and nothing else can fit. + assert(accumulated_count == 0) -- then it has to be zero, and nothing else can fit. end - table.insert(self.leafs, {hash = hash, accumulated_count = accumulated_count}) + table.insert(self.leafs, { hash = hash, accumulated_count = accumulated_count }) else - table.insert(self.leafs, {hash = hash, accumulated_count = rep}) + table.insert(self.leafs, { hash = hash, accumulated_count = rep }) end end local function merkle(leafs, log2size, stride) - local first_time = stride * (1 << log2size) + 1 + local first_time = stride * (1 << log2size) + 1 local last_time = (stride + 1) * (1 << log2size) local first_cell = leafs:find_cell_containing(first_time) @@ -109,7 +108,7 @@ local function merkle(leafs, log2size, stride) return hash_left:join(hash_right) end -function MerkleBuilder:build() +function MerkleBuilder:build(implicit_hash) local last = assert(self.leafs[#self.leafs], #self.leafs) local count = last.accumulated_count @@ -122,7 +121,7 @@ function MerkleBuilder:build() end local root_hash = merkle(Slice:new(self.leafs), log2size, 0) - return MerkleTree:new(self.leafs, root_hash, log2size) + return MerkleTree:new(self.leafs, root_hash, log2size, implicit_hash) end -- local Hash = require "cryptography.hash" diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua index 84cece02c..42b212c3a 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua @@ -1,12 +1,13 @@ local MerkleTree = {} MerkleTree.__index = MerkleTree -function MerkleTree:new(leafs, root_hash, log2size) +function MerkleTree:new(leafs, root_hash, log2size, implicit_hash) local m = { leafs = leafs, root_hash = root_hash, digest_hex = root_hash.digest_hex, log2size = log2size, + implicit_hash = implicit_hash } setmetatable(m, self) return m @@ -24,7 +25,73 @@ function MerkleTree:iterated_merkle(level) return self.root_hash:iterated_merkle(level) end --- TODO add generate proof. --- TODO add children?? +function MerkleTree:hex_string() + return self.root_hash:hex_string() +end + +MerkleTree.__tostring = function(x) + return x.root_hash:hex_string() +end + + +local function generate_proof(proof, root, height, include_index) + if height == 0 then + if include_index == 0 then + proof.leaf = root + return + else + table.insert(proof, root) + end + end + + local new_height = height - 1 + local ok, left, right = root:children() + assert(ok) + + if (include_index >> new_height) == 0 then + generate_proof(proof, left, new_height, include_index) + table.insert(proof, right) + else + generate_proof(proof, right, new_height, include_index) + table.insert(proof, left) + end +end + +function MerkleTree:prove_leaf(index) + local height + local l = assert(self.leafs[1]) + if l.log2size then + height = l.log2size + self.log2size + else + height = self.log2size + end + + assert((index >> height) == 0) + local proof = {} + generate_proof(proof, self.root_hash, height, index) + return proof.leaf, proof +end + +local function array_reverse(x) + local n, m = #x, #x / 2 + for i = 1, m do + x[i], x[n - i + 1] = x[n - i + 1], x[i] + end + return x +end + +function MerkleTree:last() + local proof = {} + local ok, left, right = self.root_hash:children() + local old_right = self.root_hash + + while ok do + table.insert(proof, left) + old_right = right + ok, left, right = right:children() + end + + return old_right, array_reverse(proof) +end return MerkleTree diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 627534073..fdb892f1d 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -29,7 +29,7 @@ end local p2 do local FakeCommitmentBuilder = require "computation.fake_commitment" - local builder = FakeCommitmentBuilder:new() + local builder = FakeCommitmentBuilder:new(initial_hash) local client = Client:new(blockchain) p2 = Player:new(contract, client, builder) end diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua index 740fbad4b..3591bcd19 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player.lua @@ -1,4 +1,5 @@ local constants = require "constants" +local bint = require 'utils.bint' (256) -- use 256 bits integers local Player = {} Player.__index = Player @@ -22,7 +23,9 @@ function Player:new(root_tournament_address, client, commitment_builder) end function Player:react() - if self.has_lost then return end + if self.has_lost then + return + end return self:_react_tournament(self.root_tournament) end @@ -47,7 +50,7 @@ function Player:_react_tournament(tournament) local tournament_winner = self.client:tournament_winner(tournament.address) if not tournament_winner:is_zero() then local old_commitment = self.commitments[tournament.parent.address] - if tournament_winner ~= old_commitment.root then + if tournament_winner ~= old_commitment.root_hash then print "player lost tournament" self.has_lost = true return @@ -64,9 +67,9 @@ function Player:_react_tournament(tournament) "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, - commitment.root + commitment.root_hash )) - local _, left, right = old_commitment:children(old_commitment.root) + local _, left, right = old_commitment:children(old_commitment.root_hash) self.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) return end @@ -84,11 +87,11 @@ end function Player:_react_match(match, commitment) -- TODO call timeout if needed - -- print("HEIGHT", match.current_height) + print("HEIGHT", match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then - local f, left, right = commitment:children(commitment.root) + local f, left, right = commitment.root_hash:children() assert(f) local finished = @@ -104,14 +107,15 @@ function Player:_react_match(match, commitment) "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, - commitment.root + commitment.root_hash )) self.client:tx_win_leaf_match( match.tournament.address, match.commitment_one, match.commitment_two, left, - right + right, + "" ) else local address = self.client:read_tournament_created( @@ -123,17 +127,18 @@ function Player:_react_match(match, commitment) new_tournament.address = address new_tournament.level = match.tournament.level - 1 new_tournament.parent = match.tournament + new_tournament.base_big_cycle = match.base_big_cycle return self:_react_tournament(new_tournament) end elseif match.current_height == 1 then -- match to be sealed - local found, left, right = commitment:children(match.current_other_parent) + local found, left, right = match.current_other_parent:children() if not found then return end local initial_hash, proof - if match.running_leaf == 0 then - initial_hash, proof = self.machine.initial_hash, {} + if match.running_leaf:iszero() then + initial_hash, proof = commitment.implicit_hash, {} else initial_hash, proof = commitment:prove_leaf(match.running_leaf) end @@ -143,7 +148,7 @@ function Player:_react_match(match, commitment) "seal leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, - commitment.root + commitment.root_hash )) self.client:tx_seal_leaf_match( match.tournament.address, @@ -159,7 +164,7 @@ function Player:_react_match(match, commitment) "seal inner match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, - commitment.root + commitment.root_hash )) self.client:tx_seal_inner_match( match.tournament.address, @@ -180,23 +185,25 @@ function Player:_react_match(match, commitment) new_tournament.address = address new_tournament.level = match.tournament.level - 1 new_tournament.parent = match.tournament - new_tournament.base_big_cycle = match.leaf_cycle + new_tournament.base_big_cycle = match.base_big_cycle return self:_react_tournament(new_tournament) end else -- match running - local found, left, right = commitment:children(match.current_other_parent.digest_hex) - if not found then return end + local found, left, right = match.current_other_parent:children() + if not found then + return + end local new_left, new_right if left ~= match.current_left then local f - f, new_left, new_right = commitment:children(left) + f, new_left, new_right = left:children() assert(f) else local f - f, new_left, new_right = commitment:children(right) + f, new_left, new_right = right:children() assert(f) end @@ -205,7 +212,7 @@ function Player:_react_match(match, commitment) match.current_height, match.tournament.address, match.tournament.level, - commitment.root + commitment.root_hash )) self.client:tx_advance_match( match.tournament.address, @@ -220,32 +227,37 @@ function Player:_react_match(match, commitment) end function Player:_latest_match(tournament, commitment) - local matches = self.client:read_match_created(tournament.address, commitment.root) + local matches = self.client:read_match_created(tournament.address, commitment.root_hash) local last_match = matches[#matches] if not last_match then return false end local m = self.client:match(tournament.address, last_match.match_id_hash) - if m[1]:is_zero() then return false end + if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then + return false + end last_match.current_other_parent = m[1] last_match.current_left = m[2] - last_match.running_leaf = tonumber(m[4]) - last_match.height = tonumber(m[5]) - last_match.current_height = tonumber(m[6]) + last_match.current_right = m[3] + last_match.running_leaf = bint(m[4]) + last_match.current_height = tonumber(m[5]) + last_match.level = tonumber(m[6]) last_match.tournament = tournament local level = tournament.level - last_match.leaf_cycle = tournament.base_big_cycle + - (last_match.running_leaf << (constants.log2step[level] + constants.log2_uarch_span)) + local base = bint(tournament.base_big_cycle) + local step = bint(1) << constants.log2step[level] + last_match.leaf_cycle = base + (step * last_match.running_leaf) + last_match.base_big_cycle = (last_match.leaf_cycle >> constants.log2_uarch_span):touinteger() return last_match end function Player:_join_tournament_if_needed(tournament, commitment) - local c = self.client:read_commitment(tournament.address, commitment.root) + local c = self.client:read_commitment(tournament.address, commitment.root_hash) if c.clock.allowance == 0 then - local f, left, right = commitment:children(commitment.root) + local f, left, right = commitment:children(commitment.root_hash) assert(f) local last, proof = commitment:last() @@ -253,7 +265,7 @@ function Player:_join_tournament_if_needed(tournament, commitment) "join tournament %s of level %d with commitment %s", tournament.address, tournament.level, - commitment.root + commitment.root_hash )) self.client:tx_join_tournament(tournament.address, last, proof, left, right) end diff --git a/onchain/permissionless-arbitration/offchain/utils/bint.lua b/onchain/permissionless-arbitration/offchain/utils/bint.lua new file mode 100644 index 000000000..64b6df71e --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/bint.lua @@ -0,0 +1,1756 @@ +--[[-- +lua-bint - v0.5.1 - 26/Jun/2023 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/lua-bint + +Small portable arbitrary-precision integer arithmetic library in pure Lua for +computing with large integers. + +Different from most arbitrary-precision integer libraries in pure Lua out there this one +uses an array of lua integers as underlying data-type in its implementation instead of +using strings or large tables, this make it efficient for working with fixed width integers +and to make bitwise operations. + +## Design goals + +The main design goal of this library is to be small, correct, self contained and use few +resources while retaining acceptable performance and feature completeness. + +The library is designed to follow recent Lua integer semantics, this means that +integer overflow warps around, +signed integers are implemented using two-complement arithmetic rules, +integer division operations rounds towards minus infinity, +any mixed operations with float numbers promotes the value to a float, +and the usual division/power operation always promotes to floats. + +The library is designed to be possible to work with only unsigned integer arithmetic +when using the proper methods. + +All the lua arithmetic operators (+, -, *, //, /, %) and bitwise operators (&, |, ~, <<, >>) +are implemented as metamethods. + +The integer size must be fixed in advance and the library is designed to be more efficient when +working with integers of sizes between 64-4096 bits. If you need to work with really huge numbers +without size restrictions then use another library. This choice has been made to have more efficiency +in that specific size range. + +## Usage + +First on you should require the bint file including how many bits the bint module will work with, +by calling the returned function from the require, for example: + +```lua +local bint = require 'bint'(1024) +``` + +For more information about its arguments see @{newmodule}. +Then when you need create a bint, you can use one of the following functions: + +* @{bint.fromuinteger} (convert from lua integers, but read as unsigned integer) +* @{bint.frominteger} (convert from lua integers, preserving the sign) +* @{bint.frombase} (convert from arbitrary bases, like hexadecimal) +* @{bint.fromstring} (convert from arbitrary string, support binary/hexadecimal/decimal) +* @{bint.trunc} (convert from lua numbers, truncating the fractional part) +* @{bint.new} (convert from anything, asserts on invalid integers) +* @{bint.tobint} (convert from anything, returns nil on invalid integers) +* @{bint.parse} (convert from anything, returns a lua number as fallback) +* @{bint.zero} +* @{bint.one} +* `bint` + +You can also call `bint` as it is an alias to `bint.new`. +In doubt use @{bint.new} to create a new bint. + +Then you can use all the usual lua numeric operations on it, +all the arithmetic metamethods are implemented. +When you are done computing and need to get the result, +get the output from one of the following functions: + +* @{bint.touinteger} (convert to a lua integer, wraps around as an unsigned integer) +* @{bint.tointeger} (convert to a lua integer, wraps around, preserves the sign) +* @{bint.tonumber} (convert to lua float, losing precision) +* @{bint.tobase} (convert to a string in any base) +* @{bint.__tostring} (convert to a string in base 10) + +To output a very large integer with no loss you probably want to use @{bint.tobase} +or call `tostring` to get a string representation. + +## Precautions + +All library functions can be mixed with lua numbers, +this makes easy to mix operations between bints and lua numbers, +however the user should take care in some situations: + +* Don't mix integers and float operations if you want to work with integers only. +* Don't use the regular equal operator ('==') to compare values from this library, +unless you know in advance that both values are of the same primitive type, +otherwise it will always return false, use @{bint.eq} to be safe. +* Don't pass fractional numbers to functions that an integer is expected +* Don't mix operations between bint classes with different sizes as this is not supported, this +will throw assertions. +* Remember that casting back to lua integers or numbers precision can be lost. +* For dividing while preserving integers use the @{bint.__idiv} (the '//' operator). +* For doing power operation preserving integers use the @{bint.ipow} function. +* Configure the proper integer size you intend to work with, otherwise large integers may wrap around. + +]] + +-- Returns number of bits of the internal lua integer type. +local function luainteger_bitsize() + local n, i = -1, 0 + repeat + n, i = n >> 16, i + 16 + until n == 0 + return i +end + +local math_type = math.type +local math_floor = math.floor +local math_abs = math.abs +local math_ceil = math.ceil +local math_modf = math.modf +local math_mininteger = math.mininteger +local math_maxinteger = math.maxinteger +local math_max = math.max +local math_min = math.min +local string_format = string.format +local table_insert = table.insert +local table_concat = table.concat +local table_unpack = table.unpack + +local memo = {} + +--- Create a new bint module representing integers of the desired bit size. +-- This is the returned function when `require 'bint'` is called. +-- @function newmodule +-- @param bits Number of bits for the integer representation, must be multiple of wordbits and +-- at least 64. +-- @param[opt] wordbits Number of the bits for the internal word, +-- defaults to half of Lua's integer size. +local function newmodule(bits, wordbits) + local intbits = luainteger_bitsize() + bits = bits or 256 + wordbits = wordbits or (intbits // 2) + + -- Memoize bint modules + local memoindex = bits * 64 + wordbits + if memo[memoindex] then + return memo[memoindex] + end + + -- Validate + assert(bits % wordbits == 0, 'bitsize is not multiple of word bitsize') + assert(2 * wordbits <= intbits, 'word bitsize must be half of the lua integer bitsize') + assert(bits >= 64, 'bitsize must be >= 64') + assert(wordbits >= 8, 'wordbits must be at least 8') + assert(bits % 8 == 0, 'bitsize must be multiple of 8') + + -- Create bint module + local bint = {} + bint.__index = bint + + --- Number of bits representing a bint instance. + bint.bits = bits + + -- Constants used internally + local BINT_BITS = bits + local BINT_BYTES = bits // 8 + local BINT_WORDBITS = wordbits + local BINT_SIZE = BINT_BITS // BINT_WORDBITS + local BINT_WORDMAX = (1 << BINT_WORDBITS) - 1 + local BINT_WORDMSB = (1 << (BINT_WORDBITS - 1)) + local BINT_LEPACKFMT = '<' .. ('I' .. (wordbits // 8)):rep(BINT_SIZE) + local BINT_MATHMININTEGER, BINT_MATHMAXINTEGER + local BINT_MININTEGER + + --- Create a new bint with 0 value. + function bint.zero() + local x = setmetatable({}, bint) + for i = 1, BINT_SIZE do + x[i] = 0 + end + return x + end + + local bint_zero = bint.zero + + --- Create a new bint with 1 value. + function bint.one() + local x = setmetatable({}, bint) + x[1] = 1 + for i = 2, BINT_SIZE do + x[i] = 0 + end + return x + end + + local bint_one = bint.one + + -- Convert a value to a lua integer without losing precision. + local function tointeger(x) + x = tonumber(x) + local ty = math_type(x) + if ty == 'float' then + local floorx = math_floor(x) + if floorx == x then + x = floorx + ty = math_type(x) + end + end + if ty == 'integer' then + return x + end + end + + --- Create a bint from an unsigned integer. + -- Treats signed integers as an unsigned integer. + -- @param x A value to initialize from convertible to a lua integer. + -- @return A new bint or nil in case the input cannot be represented by an integer. + -- @see bint.frominteger + function bint.fromuinteger(x) + x = tointeger(x) + if x then + if x == 1 then + return bint_one() + elseif x == 0 then + return bint_zero() + end + local n = setmetatable({}, bint) + for i = 1, BINT_SIZE do + n[i] = x & BINT_WORDMAX + x = x >> BINT_WORDBITS + end + return n + end + end + + local bint_fromuinteger = bint.fromuinteger + + --- Create a bint from a signed integer. + -- @param x A value to initialize from convertible to a lua integer. + -- @return A new bint or nil in case the input cannot be represented by an integer. + -- @see bint.fromuinteger + function bint.frominteger(x) + x = tointeger(x) + if x then + if x == 1 then + return bint_one() + elseif x == 0 then + return bint_zero() + end + local neg = false + if x < 0 then + x = math_abs(x) + neg = true + end + local n = setmetatable({}, bint) + for i = 1, BINT_SIZE do + n[i] = x & BINT_WORDMAX + x = x >> BINT_WORDBITS + end + if neg then + n:_unm() + end + return n + end + end + + local bint_frominteger = bint.frominteger + + local basesteps = {} + + -- Compute the read step for frombase function + local function getbasestep(base) + local step = basesteps[base] + if step then + return step + end + step = 0 + local dmax = 1 + local limit = math_maxinteger // base + repeat + step = step + 1 + dmax = dmax * base + until dmax >= limit + basesteps[base] = step + return step + end + + -- Compute power with lua integers. + local function ipow(y, x, n) + if n == 1 then + return y * x + elseif n & 1 == 0 then --even + return ipow(y, x * x, n // 2) + end + return ipow(x * y, x * x, (n - 1) // 2) + end + + --- Create a bint from a string of the desired base. + -- @param s The string to be converted from, + -- must have only alphanumeric and '+-' characters. + -- @param[opt] base Base that the number is represented, defaults to 10. + -- Must be at least 2 and at most 36. + -- @return A new bint or nil in case the conversion failed. + function bint.frombase(s, base) + if type(s) ~= 'string' then + return + end + base = base or 10 + if not (base >= 2 and base <= 36) then + -- number base is too large + return + end + local step = getbasestep(base) + if #s < step then + -- string is small, use tonumber (faster) + return bint_frominteger(tonumber(s, base)) + end + local sign, int = s:lower():match('^([+-]?)(%w+)$') + if not (sign and int) then + -- invalid integer string representation + return + end + local n = bint_zero() + for i = 1, #int, step do + local part = int:sub(i, i + step - 1) + local d = tonumber(part, base) + if not d then + -- invalid integer string representation + return + end + if i > 1 then + n = n * ipow(1, base, #part) + end + if d ~= 0 then + n:_add(d) + end + end + if sign == '-' then + n:_unm() + end + return n + end + + local bint_frombase = bint.frombase + + --- Create a new bint from a string. + -- The string can by a decimal number, binary number prefixed with '0b' or hexadecimal number prefixed with '0x'. + -- @param s A string convertible to a bint. + -- @return A new bint or nil in case the conversion failed. + -- @see bint.frombase + function bint.fromstring(s) + if type(s) ~= 'string' then + return + end + if s:find('^[+-]?[0-9]+$') then + return bint_frombase(s, 10) + elseif s:find('^[+-]?0[xX][0-9a-fA-F]+$') then + return bint_frombase(s:gsub('0[xX]', '', 1), 16) + elseif s:find('^[+-]?0[bB][01]+$') then + return bint_frombase(s:gsub('0[bB]', '', 1), 2) + end + end + + local bint_fromstring = bint.fromstring + + --- Create a new bint from a buffer of little-endian bytes. + -- @param buffer Buffer of bytes, extra bytes are trimmed from the right, missing bytes are padded to the right. + -- @raise An assert is thrown in case buffer is not an string. + -- @return A bint. + function bint.fromle(buffer) + assert(type(buffer) == 'string', 'buffer is not a string') + if #buffer > BINT_BYTES then -- trim extra bytes from the right + buffer = buffer:sub(1, BINT_BYTES) + elseif #buffer < BINT_BYTES then -- add missing bytes to the right + buffer = buffer .. ('\x00'):rep(BINT_BYTES - #buffer) + end + return setmetatable({ BINT_LEPACKFMT:unpack(buffer) }, bint) + end + + --- Create a new bint from a buffer of big-endian bytes. + -- @param buffer Buffer of bytes, extra bytes are trimmed from the left, missing bytes are padded to the left. + -- @raise An assert is thrown in case buffer is not an string. + -- @return A bint. + function bint.frombe(buffer) + assert(type(buffer) == 'string', 'buffer is not a string') + if #buffer > BINT_BYTES then -- trim extra bytes from the left + buffer = buffer:sub(-BINT_BYTES, #buffer) + elseif #buffer < BINT_BYTES then -- add missing bytes to the left + buffer = ('\x00'):rep(BINT_BYTES - #buffer) .. buffer + end + return setmetatable({ BINT_LEPACKFMT:unpack(buffer:reverse()) }, bint) + end + + --- Create a new bint from a value. + -- @param x A value convertible to a bint (string, number or another bint). + -- @return A new bint, guaranteed to be a new reference in case needed. + -- @raise An assert is thrown in case x is not convertible to a bint. + -- @see bint.tobint + -- @see bint.parse + function bint.new(x) + if getmetatable(x) ~= bint then + local ty = type(x) + if ty == 'number' then + x = bint_frominteger(x) + elseif ty == 'string' then + x = bint_fromstring(x) + end + assert(x, 'value cannot be represented by a bint') + return x + end + -- return a clone + local n = setmetatable({}, bint) + for i = 1, BINT_SIZE do + n[i] = x[i] + end + return n + end + + local bint_new = bint.new + + --- Convert a value to a bint if possible. + -- @param x A value to be converted (string, number or another bint). + -- @param[opt] clone A boolean that tells if a new bint reference should be returned. + -- Defaults to false. + -- @return A bint or nil in case the conversion failed. + -- @see bint.new + -- @see bint.parse + function bint.tobint(x, clone) + if getmetatable(x) == bint then + if not clone then + return x + end + -- return a clone + local n = setmetatable({}, bint) + for i = 1, BINT_SIZE do + n[i] = x[i] + end + return n + end + local ty = type(x) + if ty == 'number' then + return bint_frominteger(x) + elseif ty == 'string' then + return bint_fromstring(x) + end + end + + local tobint = bint.tobint + + --- Convert a value to a bint if possible otherwise to a lua number. + -- Useful to prepare values that you are unsure if it's going to be an integer or float. + -- @param x A value to be converted (string, number or another bint). + -- @param[opt] clone A boolean that tells if a new bint reference should be returned. + -- Defaults to false. + -- @return A bint or a lua number or nil in case the conversion failed. + -- @see bint.new + -- @see bint.tobint + function bint.parse(x, clone) + local i = tobint(x, clone) + if i then + return i + end + return tonumber(x) + end + + local bint_parse = bint.parse + + --- Convert a bint to an unsigned integer. + -- Note that large unsigned integers may be represented as negatives in lua integers. + -- Note that lua cannot represent values larger than 64 bits, + -- in that case integer values wrap around. + -- @param x A bint or a number to be converted into an unsigned integer. + -- @return An integer or nil in case the input cannot be represented by an integer. + -- @see bint.tointeger + function bint.touinteger(x) + if getmetatable(x) == bint then + local n = 0 + for i = 1, BINT_SIZE do + n = n | (x[i] << (BINT_WORDBITS * (i - 1))) + end + return n + end + return tointeger(x) + end + + --- Convert a bint to a signed integer. + -- It works by taking absolute values then applying the sign bit in case needed. + -- Note that lua cannot represent values larger than 64 bits, + -- in that case integer values wrap around. + -- @param x A bint or value to be converted into an unsigned integer. + -- @return An integer or nil in case the input cannot be represented by an integer. + -- @see bint.touinteger + function bint.tointeger(x) + if getmetatable(x) == bint then + local n = 0 + local neg = x:isneg() + if neg then + x = -x + end + for i = 1, BINT_SIZE do + n = n | (x[i] << (BINT_WORDBITS * (i - 1))) + end + if neg then + n = -n + end + return n + end + return tointeger(x) + end + + local bint_tointeger = bint.tointeger + + local function bint_assert_tointeger(x) + x = bint_tointeger(x) + if not x then + error('value has no integer representation') + end + return x + end + + --- Convert a bint to a lua float in case integer would wrap around or lua integer otherwise. + -- Different from @{bint.tointeger} the operation does not wrap around integers, + -- but digits precision are lost in the process of converting to a float. + -- @param x A bint or value to be converted into a lua number. + -- @return A lua number or nil in case the input cannot be represented by a number. + -- @see bint.tointeger + function bint.tonumber(x) + if getmetatable(x) == bint then + if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then + return x:tointeger() + end + return tonumber(tostring(x)) + end + return tonumber(x) + end + + local bint_tonumber = bint.tonumber + + -- Compute base letters to use in bint.tobase + local BASE_LETTERS = {} + do + for i = 1, 36 do + BASE_LETTERS[i - 1] = ('0123456789abcdefghijklmnopqrstuvwxyz'):sub(i, i) + end + end + + --- Convert a bint to a string in the desired base. + -- @param x The bint to be converted from. + -- @param[opt] base Base to be represented, defaults to 10. + -- Must be at least 2 and at most 36. + -- @param[opt] unsigned Whether to output as an unsigned integer. + -- Defaults to false for base 10 and true for others. + -- When unsigned is false the symbol '-' is prepended in negative values. + -- @return A string representing the input. + -- @raise An assert is thrown in case the base is invalid. + function bint.tobase(x, base, unsigned) + x = tobint(x) + if not x then + -- x is a fractional float or something else + return + end + base = base or 10 + if not (base >= 2 and base <= 36) then + -- number base is too large + return + end + if unsigned == nil then + unsigned = base ~= 10 + end + local isxneg = x:isneg() + if (base == 10 and not unsigned) or (base == 16 and unsigned and not isxneg) then + if x <= BINT_MATHMAXINTEGER and x >= BINT_MATHMININTEGER then + -- integer is small, use tostring or string.format (faster) + local n = x:tointeger() + if base == 10 then + return tostring(n) + elseif unsigned then + return string_format('%x', n) + end + end + end + local ss = {} + local neg = not unsigned and isxneg + x = neg and x:abs() or bint_new(x) + local xiszero = x:iszero() + if xiszero then + return '0' + end + -- calculate basepow + local step = 0 + local basepow = 1 + local limit = (BINT_WORDMSB - 1) // base + repeat + step = step + 1 + basepow = basepow * base + until basepow >= limit + -- serialize base digits + local size = BINT_SIZE + local xd, carry, d + repeat + -- single word division + carry = 0 + xiszero = true + for i = size, 1, -1 do + carry = carry | x[i] + d, xd = carry // basepow, carry % basepow + if xiszero and d ~= 0 then + size = i + xiszero = false + end + x[i] = d + carry = xd << BINT_WORDBITS + end + -- digit division + for _ = 1, step do + xd, d = xd // base, xd % base + if xiszero and xd == 0 and d == 0 then + -- stop on leading zeros + break + end + table_insert(ss, 1, BASE_LETTERS[d]) + end + until xiszero + if neg then + table_insert(ss, 1, '-') + end + return table_concat(ss) + end + + local function bint_assert_convert(x) + return assert(tobint(x), 'value has not integer representation') + end + + --- Convert a bint to a buffer of little-endian bytes. + -- @param x A bint or lua integer. + -- @param[opt] trim If true, zero bytes on the right are trimmed. + -- @return A buffer of bytes representing the input. + -- @raise Asserts in case input is not convertible to an integer. + function bint.tole(x, trim) + x = bint_assert_convert(x) + local s = BINT_LEPACKFMT:pack(table_unpack(x)) + if trim then + s = s:gsub('\x00+$', '') + if s == '' then + s = '\x00' + end + end + return s + end + + --- Convert a bint to a buffer of big-endian bytes. + -- @param x A bint or lua integer. + -- @param[opt] trim If true, zero bytes on the left are trimmed. + -- @return A buffer of bytes representing the input. + -- @raise Asserts in case input is not convertible to an integer. + function bint.tobe(x, trim) + x = bint_assert_convert(x) + local s = BINT_LEPACKFMT:pack(table_unpack(x)):reverse() + if trim then + s = s:gsub('^\x00+', '') + if s == '' then + s = '\x00' + end + end + return s + end + + --- Check if a number is 0 considering bints. + -- @param x A bint or a lua number. + function bint.iszero(x) + if getmetatable(x) == bint then + for i = 1, BINT_SIZE do + if x[i] ~= 0 then + return false + end + end + return true + end + return x == 0 + end + + --- Check if a number is 1 considering bints. + -- @param x A bint or a lua number. + function bint.isone(x) + if getmetatable(x) == bint then + if x[1] ~= 1 then + return false + end + for i = 2, BINT_SIZE do + if x[i] ~= 0 then + return false + end + end + return true + end + return x == 1 + end + + --- Check if a number is -1 considering bints. + -- @param x A bint or a lua number. + function bint.isminusone(x) + if getmetatable(x) == bint then + for i = 1, BINT_SIZE do + if x[i] ~= BINT_WORDMAX then + return false + end + end + return true + end + return x == -1 + end + + local bint_isminusone = bint.isminusone + + --- Check if the input is a bint. + -- @param x Any lua value. + function bint.isbint(x) + return getmetatable(x) == bint + end + + --- Check if the input is a lua integer or a bint. + -- @param x Any lua value. + function bint.isintegral(x) + return getmetatable(x) == bint or math_type(x) == 'integer' + end + + --- Check if the input is a bint or a lua number. + -- @param x Any lua value. + function bint.isnumeric(x) + return getmetatable(x) == bint or type(x) == 'number' + end + + --- Get the number type of the input (bint, integer or float). + -- @param x Any lua value. + -- @return Returns "bint" for bints, "integer" for lua integers, + -- "float" from lua floats or nil otherwise. + function bint.type(x) + if getmetatable(x) == bint then + return 'bint' + end + return math_type(x) + end + + --- Check if a number is negative considering bints. + -- Zero is guaranteed to never be negative for bints. + -- @param x A bint or a lua number. + function bint.isneg(x) + if getmetatable(x) == bint then + return x[BINT_SIZE] & BINT_WORDMSB ~= 0 + end + return x < 0 + end + + local bint_isneg = bint.isneg + + --- Check if a number is positive considering bints. + -- @param x A bint or a lua number. + function bint.ispos(x) + if getmetatable(x) == bint then + return not x:isneg() and not x:iszero() + end + return x > 0 + end + + --- Check if a number is even considering bints. + -- @param x A bint or a lua number. + function bint.iseven(x) + if getmetatable(x) == bint then + return x[1] & 1 == 0 + end + return math_abs(x) % 2 == 0 + end + + --- Check if a number is odd considering bints. + -- @param x A bint or a lua number. + function bint.isodd(x) + if getmetatable(x) == bint then + return x[1] & 1 == 1 + end + return math_abs(x) % 2 == 1 + end + + --- Create a new bint with the maximum possible integer value. + function bint.maxinteger() + local x = setmetatable({}, bint) + for i = 1, BINT_SIZE - 1 do + x[i] = BINT_WORDMAX + end + x[BINT_SIZE] = BINT_WORDMAX ~ BINT_WORDMSB + return x + end + + --- Create a new bint with the minimum possible integer value. + function bint.mininteger() + local x = setmetatable({}, bint) + for i = 1, BINT_SIZE - 1 do + x[i] = 0 + end + x[BINT_SIZE] = BINT_WORDMSB + return x + end + + --- Bitwise left shift a bint in one bit (in-place). + function bint:_shlone() + local wordbitsm1 = BINT_WORDBITS - 1 + for i = BINT_SIZE, 2, -1 do + self[i] = ((self[i] << 1) | (self[i - 1] >> wordbitsm1)) & BINT_WORDMAX + end + self[1] = (self[1] << 1) & BINT_WORDMAX + return self + end + + --- Bitwise right shift a bint in one bit (in-place). + function bint:_shrone() + local wordbitsm1 = BINT_WORDBITS - 1 + for i = 1, BINT_SIZE - 1 do + self[i] = ((self[i] >> 1) | (self[i + 1] << wordbitsm1)) & BINT_WORDMAX + end + self[BINT_SIZE] = self[BINT_SIZE] >> 1 + return self + end + + -- Bitwise left shift words of a bint (in-place). Used only internally. + function bint:_shlwords(n) + for i = BINT_SIZE, n + 1, -1 do + self[i] = self[i - n] + end + for i = 1, n do + self[i] = 0 + end + return self + end + + -- Bitwise right shift words of a bint (in-place). Used only internally. + function bint:_shrwords(n) + if n < BINT_SIZE then + for i = 1, BINT_SIZE - n do + self[i] = self[i + n] + end + for i = BINT_SIZE - n + 1, BINT_SIZE do + self[i] = 0 + end + else + for i = 1, BINT_SIZE do + self[i] = 0 + end + end + return self + end + + --- Increment a bint by one (in-place). + function bint:_inc() + for i = 1, BINT_SIZE do + local tmp = self[i] + local v = (tmp + 1) & BINT_WORDMAX + self[i] = v + if v > tmp then + break + end + end + return self + end + + --- Increment a number by one considering bints. + -- @param x A bint or a lua number to increment. + function bint.inc(x) + local ix = tobint(x, true) + if ix then + return ix:_inc() + end + return x + 1 + end + + --- Decrement a bint by one (in-place). + function bint:_dec() + for i = 1, BINT_SIZE do + local tmp = self[i] + local v = (tmp - 1) & BINT_WORDMAX + self[i] = v + if v <= tmp then + break + end + end + return self + end + + --- Decrement a number by one considering bints. + -- @param x A bint or a lua number to decrement. + function bint.dec(x) + local ix = tobint(x, true) + if ix then + return ix:_dec() + end + return x - 1 + end + + --- Assign a bint to a new value (in-place). + -- @param y A value to be copied from. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_assign(y) + y = bint_assert_convert(y) + for i = 1, BINT_SIZE do + self[i] = y[i] + end + return self + end + + --- Take absolute of a bint (in-place). + function bint:_abs() + if self:isneg() then + self:_unm() + end + return self + end + + --- Take absolute of a number considering bints. + -- @param x A bint or a lua number to take the absolute. + function bint.abs(x) + local ix = tobint(x, true) + if ix then + return ix:_abs() + end + return math_abs(x) + end + + local bint_abs = bint.abs + + --- Take the floor of a number considering bints. + -- @param x A bint or a lua number to perform the floor operation. + function bint.floor(x) + if getmetatable(x) == bint then + return bint_new(x) + end + return bint_new(math_floor(tonumber(x))) + end + + --- Take ceil of a number considering bints. + -- @param x A bint or a lua number to perform the ceil operation. + function bint.ceil(x) + if getmetatable(x) == bint then + return bint_new(x) + end + return bint_new(math_ceil(tonumber(x))) + end + + --- Wrap around bits of an integer (discarding left bits) considering bints. + -- @param x A bint or a lua integer. + -- @param y Number of right bits to preserve. + function bint.bwrap(x, y) + x = bint_assert_convert(x) + if y <= 0 then + return bint_zero() + elseif y < BINT_BITS then + return x & (bint_one() << y):_dec() + end + return bint_new(x) + end + + --- Rotate left integer x by y bits considering bints. + -- @param x A bint or a lua integer. + -- @param y Number of bits to rotate. + function bint.brol(x, y) + x, y = bint_assert_convert(x), bint_assert_tointeger(y) + if y > 0 then + return (x << y) | (x >> (BINT_BITS - y)) + elseif y < 0 then + if y ~= math_mininteger then + return x:bror(-y) + else + x:bror(-(y + 1)) + x:bror(1) + end + end + return x + end + + --- Rotate right integer x by y bits considering bints. + -- @param x A bint or a lua integer. + -- @param y Number of bits to rotate. + function bint.bror(x, y) + x, y = bint_assert_convert(x), bint_assert_tointeger(y) + if y > 0 then + return (x >> y) | (x << (BINT_BITS - y)) + elseif y < 0 then + if y ~= math_mininteger then + return x:brol(-y) + else + x:brol(-(y + 1)) + x:brol(1) + end + end + return x + end + + --- Truncate a number to a bint. + -- Floats numbers are truncated, that is, the fractional port is discarded. + -- @param x A number to truncate. + -- @return A new bint or nil in case the input does not fit in a bint or is not a number. + function bint.trunc(x) + if getmetatable(x) ~= bint then + x = tonumber(x) + if x then + local ty = math_type(x) + if ty == 'float' then + -- truncate to integer + x = math_modf(x) + end + return bint_frominteger(x) + end + return + end + return bint_new(x) + end + + --- Take maximum between two numbers considering bints. + -- @param x A bint or lua number to compare. + -- @param y A bint or lua number to compare. + -- @return A bint or a lua number. Guarantees to return a new bint for integer values. + function bint.max(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return bint_new(ix > iy and ix or iy) + end + return bint_parse(math_max(x, y)) + end + + --- Take minimum between two numbers considering bints. + -- @param x A bint or lua number to compare. + -- @param y A bint or lua number to compare. + -- @return A bint or a lua number. Guarantees to return a new bint for integer values. + function bint.min(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return bint_new(ix < iy and ix or iy) + end + return bint_parse(math_min(x, y)) + end + + --- Add an integer to a bint (in-place). + -- @param y An integer to be added. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_add(y) + y = bint_assert_convert(y) + local carry = 0 + for i = 1, BINT_SIZE do + local tmp = self[i] + y[i] + carry + carry = tmp >> BINT_WORDBITS + self[i] = tmp & BINT_WORDMAX + end + return self + end + + --- Add two numbers considering bints. + -- @param x A bint or a lua number to be added. + -- @param y A bint or a lua number to be added. + function bint.__add(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = setmetatable({}, bint) + local carry = 0 + for i = 1, BINT_SIZE do + local tmp = ix[i] + iy[i] + carry + carry = tmp >> BINT_WORDBITS + z[i] = tmp & BINT_WORDMAX + end + return z + end + return bint_tonumber(x) + bint_tonumber(y) + end + + --- Subtract an integer from a bint (in-place). + -- @param y An integer to subtract. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_sub(y) + y = bint_assert_convert(y) + local borrow = 0 + local wordmaxp1 = BINT_WORDMAX + 1 + for i = 1, BINT_SIZE do + local res = self[i] + wordmaxp1 - y[i] - borrow + self[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + return self + end + + --- Subtract two numbers considering bints. + -- @param x A bint or a lua number to be subtracted from. + -- @param y A bint or a lua number to subtract. + function bint.__sub(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = setmetatable({}, bint) + local borrow = 0 + local wordmaxp1 = BINT_WORDMAX + 1 + for i = 1, BINT_SIZE do + local res = ix[i] + wordmaxp1 - iy[i] - borrow + z[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + return z + end + return bint_tonumber(x) - bint_tonumber(y) + end + + --- Multiply two numbers considering bints. + -- @param x A bint or a lua number to multiply. + -- @param y A bint or a lua number to multiply. + function bint.__mul(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local z = bint_zero() + local sizep1 = BINT_SIZE + 1 + local s = sizep1 + local e = 0 + for i = 1, BINT_SIZE do + if ix[i] ~= 0 or iy[i] ~= 0 then + e = math_max(e, i) + s = math_min(s, i) + end + end + for i = s, e do + for j = s, math_min(sizep1 - i, e) do + local a = ix[i] * iy[j] + if a ~= 0 then + local carry = 0 + for k = i + j - 1, BINT_SIZE do + local tmp = z[k] + (a & BINT_WORDMAX) + carry + carry = tmp >> BINT_WORDBITS + z[k] = tmp & BINT_WORDMAX + a = a >> BINT_WORDBITS + end + end + end + end + return z + end + return bint_tonumber(x) * bint_tonumber(y) + end + + --- Check if bints are equal. + -- @param x A bint to compare. + -- @param y A bint to compare. + function bint.__eq(x, y) + for i = 1, BINT_SIZE do + if x[i] ~= y[i] then + return false + end + end + return true + end + + --- Check if numbers are equal considering bints. + -- @param x A bint or lua number to compare. + -- @param y A bint or lua number to compare. + function bint.eq(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + return ix == iy + end + return x == y + end + + local bint_eq = bint.eq + + local function findleftbit(x) + for i = BINT_SIZE, 1, -1 do + local v = x[i] + if v ~= 0 then + local j = 0 + repeat + v = v >> 1 + j = j + 1 + until v == 0 + return (i - 1) * BINT_WORDBITS + j - 1, i + end + end + end + + -- Single word division modulus + local function sudivmod(nume, deno) + local rema + local carry = 0 + for i = BINT_SIZE, 1, -1 do + carry = carry | nume[i] + nume[i] = carry // deno + rema = carry % deno + carry = rema << BINT_WORDBITS + end + return rema + end + + --- Perform unsigned division and modulo operation between two integers considering bints. + -- This is effectively the same of @{bint.udiv} and @{bint.umod}. + -- @param x The numerator, must be a bint or a lua integer. + -- @param y The denominator, must be a bint or a lua integer. + -- @return The quotient following the remainder, both bints. + -- @raise Asserts on attempt to divide by zero + -- or if inputs are not convertible to integers. + -- @see bint.udiv + -- @see bint.umod + function bint.udivmod(x, y) + local nume = bint_new(x) + local deno = bint_assert_convert(y) + -- compute if high bits of denominator are all zeros + local ishighzero = true + for i = 2, BINT_SIZE do + if deno[i] ~= 0 then + ishighzero = false + break + end + end + if ishighzero then + -- try to divide by a single word (optimization) + local low = deno[1] + assert(low ~= 0, 'attempt to divide by zero') + if low == 1 then + -- denominator is one + return nume, bint_zero() + elseif low <= (BINT_WORDMSB - 1) then + -- can do single word division + local rema = sudivmod(nume, low) + return nume, bint_fromuinteger(rema) + end + end + if nume:ult(deno) then + -- denominator is greater than numerator + return bint_zero(), nume + end + -- align leftmost digits in numerator and denominator + local denolbit = findleftbit(deno) + local numelbit, numesize = findleftbit(nume) + local bit = numelbit - denolbit + deno = deno << bit + local wordmaxp1 = BINT_WORDMAX + 1 + local wordbitsm1 = BINT_WORDBITS - 1 + local denosize = numesize + local quot = bint_zero() + while bit >= 0 do + -- compute denominator <= numerator + local le = true + local size = math_max(numesize, denosize) + for i = size, 1, -1 do + local a, b = deno[i], nume[i] + if a ~= b then + le = a < b + break + end + end + -- if the portion of the numerator above the denominator is greater or equal than to the denominator + if le then + -- subtract denominator from the portion of the numerator + local borrow = 0 + for i = 1, size do + local res = nume[i] + wordmaxp1 - deno[i] - borrow + nume[i] = res & BINT_WORDMAX + borrow = (res >> BINT_WORDBITS) ~ 1 + end + -- concatenate 1 to the right bit of the quotient + local i = (bit // BINT_WORDBITS) + 1 + quot[i] = quot[i]| (1 << (bit % BINT_WORDBITS)) + end + -- shift right the denominator in one bit + for i = 1, denosize - 1 do + deno[i] = ((deno[i] >> 1) | (deno[i + 1] << wordbitsm1)) & BINT_WORDMAX + end + local lastdenoword = deno[denosize] >> 1 + deno[denosize] = lastdenoword + -- recalculate denominator size (optimization) + if lastdenoword == 0 then + while deno[denosize] == 0 do + denosize = denosize - 1 + end + if denosize == 0 then + break + end + end + -- decrement current set bit for the quotient + bit = bit - 1 + end + -- the remaining numerator is the remainder + return quot, nume + end + + local bint_udivmod = bint.udivmod + + --- Perform unsigned division between two integers considering bints. + -- @param x The numerator, must be a bint or a lua integer. + -- @param y The denominator, must be a bint or a lua integer. + -- @return The quotient, a bint. + -- @raise Asserts on attempt to divide by zero + -- or if inputs are not convertible to integers. + function bint.udiv(x, y) + return (bint_udivmod(x, y)) + end + + --- Perform unsigned integer modulo operation between two integers considering bints. + -- @param x The numerator, must be a bint or a lua integer. + -- @param y The denominator, must be a bint or a lua integer. + -- @return The remainder, a bint. + -- @raise Asserts on attempt to divide by zero + -- or if the inputs are not convertible to integers. + function bint.umod(x, y) + local _, rema = bint_udivmod(x, y) + return rema + end + + local bint_umod = bint.umod + + --- Perform integer truncate division and modulo operation between two numbers considering bints. + -- This is effectively the same of @{bint.tdiv} and @{bint.tmod}. + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The quotient following the remainder, both bint or lua number. + -- @raise Asserts on attempt to divide by zero or on division overflow. + -- @see bint.tdiv + -- @see bint.tmod + function bint.tdivmod(x, y) + local ax, ay = bint_abs(x), bint_abs(y) + local ix, iy = tobint(ax), tobint(ay) + local quot, rema + if ix and iy then + assert(not (bint_eq(x, BINT_MININTEGER) and bint_isminusone(y)), 'division overflow') + quot, rema = bint_udivmod(ix, iy) + else + quot, rema = ax // ay, ax % ay + end + local isxneg, isyneg = bint_isneg(x), bint_isneg(y) + if isxneg ~= isyneg then + quot = -quot + end + if isxneg then + rema = -rema + end + return quot, rema + end + + local bint_tdivmod = bint.tdivmod + + --- Perform truncate division between two numbers considering bints. + -- Truncate division is a division that rounds the quotient towards zero. + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The quotient, a bint or lua number. + -- @raise Asserts on attempt to divide by zero or on division overflow. + function bint.tdiv(x, y) + return (bint_tdivmod(x, y)) + end + + --- Perform integer truncate modulo operation between two numbers considering bints. + -- The operation is defined as the remainder of the truncate division + -- (division that rounds the quotient towards zero). + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The remainder, a bint or lua number. + -- @raise Asserts on attempt to divide by zero or on division overflow. + function bint.tmod(x, y) + local _, rema = bint_tdivmod(x, y) + return rema + end + + --- Perform integer floor division and modulo operation between two numbers considering bints. + -- This is effectively the same of @{bint.__idiv} and @{bint.__mod}. + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The quotient following the remainder, both bint or lua number. + -- @raise Asserts on attempt to divide by zero. + -- @see bint.__idiv + -- @see bint.__mod + function bint.idivmod(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if isnumeneg then + ix = -ix + end + if isdenoneg then + iy = -iy + end + local quot, rema = bint_udivmod(ix, iy) + if isnumeneg ~= isdenoneg then + quot:_unm() + -- round quotient towards minus infinity + if not rema:iszero() then + quot:_dec() + -- adjust the remainder + if isnumeneg and not isdenoneg then + rema:_unm():_add(y) + elseif isdenoneg and not isnumeneg then + rema:_add(y) + end + end + elseif isnumeneg then + -- adjust the remainder + rema:_unm() + end + return quot, rema + end + local nx, ny = bint_tonumber(x), bint_tonumber(y) + return nx // ny, nx % ny + end + + local bint_idivmod = bint.idivmod + + --- Perform floor division between two numbers considering bints. + -- Floor division is a division that rounds the quotient towards minus infinity, + -- resulting in the floor of the division of its operands. + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The quotient, a bint or lua number. + -- @raise Asserts on attempt to divide by zero. + function bint.__idiv(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local isnumeneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local isdenoneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if isnumeneg then + ix = -ix + end + if isdenoneg then + iy = -iy + end + local quot, rema = bint_udivmod(ix, iy) + if isnumeneg ~= isdenoneg then + quot:_unm() + -- round quotient towards minus infinity + if not rema:iszero() then + quot:_dec() + end + end + return quot, rema + end + return bint_tonumber(x) // bint_tonumber(y) + end + + --- Perform division between two numbers considering bints. + -- This always casts inputs to floats, for integer division only use @{bint.__idiv}. + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The quotient, a lua number. + function bint.__div(x, y) + return bint_tonumber(x) / bint_tonumber(y) + end + + --- Perform integer floor modulo operation between two numbers considering bints. + -- The operation is defined as the remainder of the floor division + -- (division that rounds the quotient towards minus infinity). + -- @param x The numerator, a bint or lua number. + -- @param y The denominator, a bint or lua number. + -- @return The remainder, a bint or lua number. + -- @raise Asserts on attempt to divide by zero. + function bint.__mod(x, y) + local _, rema = bint_idivmod(x, y) + return rema + end + + --- Perform integer power between two integers considering bints. + -- If y is negative then pow is performed as an unsigned integer. + -- @param x The base, an integer. + -- @param y The exponent, an integer. + -- @return The result of the pow operation, a bint. + -- @raise Asserts in case inputs are not convertible to integers. + -- @see bint.__pow + -- @see bint.upowmod + function bint.ipow(x, y) + y = bint_assert_convert(y) + if y:iszero() then + return bint_one() + elseif y:isone() then + return bint_new(x) + end + -- compute exponentiation by squaring + x, y = bint_new(x), bint_new(y) + local z = bint_one() + repeat + if y:iseven() then + x = x * x + y:_shrone() + else + z = x * z + x = x * x + y:_dec():_shrone() + end + until y:isone() + return x * z + end + + --- Perform integer power between two unsigned integers over a modulus considering bints. + -- @param x The base, an integer. + -- @param y The exponent, an integer. + -- @param m The modulus, an integer. + -- @return The result of the pow operation, a bint. + -- @raise Asserts in case inputs are not convertible to integers. + -- @see bint.__pow + -- @see bint.ipow + function bint.upowmod(x, y, m) + m = bint_assert_convert(m) + if m:isone() then + return bint_zero() + end + x, y = bint_new(x), bint_new(y) + local z = bint_one() + x = bint_umod(x, m) + while not y:iszero() do + if y:isodd() then + z = bint_umod(z * x, m) + end + y:_shrone() + x = bint_umod(x * x, m) + end + return z + end + + --- Perform numeric power between two numbers considering bints. + -- This always casts inputs to floats, for integer power only use @{bint.ipow}. + -- @param x The base, a bint or lua number. + -- @param y The exponent, a bint or lua number. + -- @return The result of the pow operation, a lua number. + -- @see bint.ipow + function bint.__pow(x, y) + return bint_tonumber(x) ^ bint_tonumber(y) + end + + --- Bitwise left shift integers considering bints. + -- @param x An integer to perform the bitwise shift. + -- @param y An integer with the number of bits to shift. + -- @return The result of shift operation, a bint. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__shl(x, y) + x, y = bint_new(x), bint_assert_tointeger(y) + if y == math_mininteger or math_abs(y) >= BINT_BITS then + return bint_zero() + end + if y < 0 then + return x >> -y + end + local nvals = y // BINT_WORDBITS + if nvals ~= 0 then + x:_shlwords(nvals) + y = y - nvals * BINT_WORDBITS + end + if y ~= 0 then + local wordbitsmy = BINT_WORDBITS - y + for i = BINT_SIZE, 2, -1 do + x[i] = ((x[i] << y) | (x[i - 1] >> wordbitsmy)) & BINT_WORDMAX + end + x[1] = (x[1] << y) & BINT_WORDMAX + end + return x + end + + --- Bitwise right shift integers considering bints. + -- @param x An integer to perform the bitwise shift. + -- @param y An integer with the number of bits to shift. + -- @return The result of shift operation, a bint. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__shr(x, y) + x, y = bint_new(x), bint_assert_tointeger(y) + if y == math_mininteger or math_abs(y) >= BINT_BITS then + return bint_zero() + end + if y < 0 then + return x << -y + end + local nvals = y // BINT_WORDBITS + if nvals ~= 0 then + x:_shrwords(nvals) + y = y - nvals * BINT_WORDBITS + end + if y ~= 0 then + local wordbitsmy = BINT_WORDBITS - y + for i = 1, BINT_SIZE - 1 do + x[i] = ((x[i] >> y) | (x[i + 1] << wordbitsmy)) & BINT_WORDMAX + end + x[BINT_SIZE] = x[BINT_SIZE] >> y + end + return x + end + + --- Bitwise AND bints (in-place). + -- @param y An integer to perform bitwise AND. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_band(y) + y = bint_assert_convert(y) + for i = 1, BINT_SIZE do + self[i] = self[i] & y[i] + end + return self + end + + --- Bitwise AND two integers considering bints. + -- @param x An integer to perform bitwise AND. + -- @param y An integer to perform bitwise AND. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__band(x, y) + return bint_new(x):_band(y) + end + + --- Bitwise OR bints (in-place). + -- @param y An integer to perform bitwise OR. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_bor(y) + y = bint_assert_convert(y) + for i = 1, BINT_SIZE do + self[i] = self[i]| y[i] + end + return self + end + + --- Bitwise OR two integers considering bints. + -- @param x An integer to perform bitwise OR. + -- @param y An integer to perform bitwise OR. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__bor(x, y) + return bint_new(x):_bor(y) + end + + --- Bitwise XOR bints (in-place). + -- @param y An integer to perform bitwise XOR. + -- @raise Asserts in case inputs are not convertible to integers. + function bint:_bxor(y) + y = bint_assert_convert(y) + for i = 1, BINT_SIZE do + self[i] = self[i] ~ y[i] + end + return self + end + + --- Bitwise XOR two integers considering bints. + -- @param x An integer to perform bitwise XOR. + -- @param y An integer to perform bitwise XOR. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__bxor(x, y) + return bint_new(x):_bxor(y) + end + + --- Bitwise NOT a bint (in-place). + function bint:_bnot() + for i = 1, BINT_SIZE do + self[i] = (~self[i]) & BINT_WORDMAX + end + return self + end + + --- Bitwise NOT a bint. + -- @param x An integer to perform bitwise NOT. + -- @raise Asserts in case inputs are not convertible to integers. + function bint.__bnot(x) + local y = setmetatable({}, bint) + for i = 1, BINT_SIZE do + y[i] = (~x[i]) & BINT_WORDMAX + end + return y + end + + --- Negate a bint (in-place). This effectively applies two's complements. + function bint:_unm() + return self:_bnot():_inc() + end + + --- Negate a bint. This effectively applies two's complements. + -- @param x A bint to perform negation. + function bint.__unm(x) + return (~x):_inc() + end + + --- Compare if integer x is less than y considering bints (unsigned version). + -- @param x Left integer to compare. + -- @param y Right integer to compare. + -- @raise Asserts in case inputs are not convertible to integers. + -- @see bint.__lt + function bint.ult(x, y) + x, y = bint_assert_convert(x), bint_assert_convert(y) + for i = BINT_SIZE, 1, -1 do + local a, b = x[i], y[i] + if a ~= b then + return a < b + end + end + return false + end + + --- Compare if bint x is less or equal than y considering bints (unsigned version). + -- @param x Left integer to compare. + -- @param y Right integer to compare. + -- @raise Asserts in case inputs are not convertible to integers. + -- @see bint.__le + function bint.ule(x, y) + x, y = bint_assert_convert(x), bint_assert_convert(y) + for i = BINT_SIZE, 1, -1 do + local a, b = x[i], y[i] + if a ~= b then + return a < b + end + end + return true + end + + --- Compare if number x is less than y considering bints and signs. + -- @param x Left value to compare, a bint or lua number. + -- @param y Right value to compare, a bint or lua number. + -- @see bint.ult + function bint.__lt(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if xneg == yneg then + for i = BINT_SIZE, 1, -1 do + local a, b = ix[i], iy[i] + if a ~= b then + return a < b + end + end + return false + end + return xneg and not yneg + end + return bint_tonumber(x) < bint_tonumber(y) + end + + --- Compare if number x is less or equal than y considering bints and signs. + -- @param x Left value to compare, a bint or lua number. + -- @param y Right value to compare, a bint or lua number. + -- @see bint.ule + function bint.__le(x, y) + local ix, iy = tobint(x), tobint(y) + if ix and iy then + local xneg = ix[BINT_SIZE] & BINT_WORDMSB ~= 0 + local yneg = iy[BINT_SIZE] & BINT_WORDMSB ~= 0 + if xneg == yneg then + for i = BINT_SIZE, 1, -1 do + local a, b = ix[i], iy[i] + if a ~= b then + return a < b + end + end + return true + end + return xneg and not yneg + end + return bint_tonumber(x) <= bint_tonumber(y) + end + + --- Convert a bint to a string on base 10. + -- @see bint.tobase + function bint:__tostring() + return self:tobase(10) + end + + -- Allow creating bints by calling bint itself + setmetatable(bint, { + __call = function(_, x) + return bint_new(x) + end + }) + + BINT_MATHMININTEGER, BINT_MATHMAXINTEGER = bint_new(math.mininteger), bint_new(math.maxinteger) + BINT_MININTEGER = bint.mininteger() + memo[memoindex] = bint + + return bint +end + +return newmodule diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol index 7472cd62b..bd4f2a5f1 100644 --- a/onchain/permissionless-arbitration/src/CanonicalConstants.sol +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -15,8 +15,8 @@ library ArbitrationConstants { // Time.Duration.wrap(60 * 60 * 24 * 7); // TODO // Dummy - Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(45); - Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(45); + Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(240); + Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(180); Time.Duration constant DISPUTE_TIMEOUT = Time.Duration.wrap( @@ -25,15 +25,14 @@ library ArbitrationConstants { ); // 4-level tournament - uint64 constant LEVELS = 4; + uint64 constant LEVELS = 3; // uint64 constant LOG2_MAX_MCYCLE = 63; /// @return log2step gap of each leaf in the tournament[level] function log2step(uint64 level) internal pure returns (uint64) { uint64[LEVELS] memory arr = [ - uint64(24), - uint64(14), - uint64(7), + uint64(31), + uint64(16), uint64(0) ]; return arr[level]; @@ -42,10 +41,9 @@ library ArbitrationConstants { /// @return height of the tournament[level] tree which is calculated by subtracting the log2step[level] from the log2step[level - 1] function height(uint64 level) internal pure returns (uint64) { uint64[LEVELS] memory arr = [ - uint64(39), - uint64(10), - uint64(7), - uint64(7) + uint64(32), + uint64(15), + uint64(16) ]; return arr[level]; } diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index 8dede2d09..a52dff70a 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -66,13 +66,13 @@ library Commitment { bytes32[] calldata hashProof ) internal pure { uint64 treeHeight = ArbitrationConstants.height(level); - Tree.Node expectedCommitment = getRootForLastLeaf( - treeHeight, - Machine.Hash.unwrap(finalState), - hashProof - ); + // Tree.Node expectedCommitment = getRootForLastLeaf( + // treeHeight, + // Machine.Hash.unwrap(finalState), + // hashProof + // ); - require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); + // require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); } From 0c3dcb80c53b33af2e388d975b1ba9a460b784f1 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Fri, 18 Aug 2023 16:32:56 +0200 Subject: [PATCH 16/25] feat(arbitration): add merkle proof verification --- .../offchain/computation/fake_commitment.lua | 11 ++++++-- .../offchain/cryptography/merkle_tree.lua | 28 ++++++++++++++----- .../offchain/entrypoint.lua | 9 +++++- .../src/Commitment.sol | 17 ++++++----- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua index c1a443827..e724cfb0a 100644 --- a/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/fake_commitment.lua @@ -5,15 +5,20 @@ local consts = require "constants" local CommitmentBuilder = {} CommitmentBuilder.__index = CommitmentBuilder -function CommitmentBuilder:new(initial_hash) - local c = { initial_hash = initial_hash } +function CommitmentBuilder:new(initial_hash, second_state) + local c = { initial_hash = initial_hash, second_state = second_state } setmetatable(c, self) return c end function CommitmentBuilder:build(_, level) local builder = MerkleBuilder:new() - builder:add(Hash.zero, 1 << consts.heights[consts.levels - level + 1]) + if consts.log2step[consts.levels - level + 1] == 0 and self.second_state then + builder:add(self.second_state) + builder:add(Hash.zero, (1 << consts.heights[consts.levels - level + 1]) - 1) + else + builder:add(Hash.zero, 1 << consts.heights[consts.levels - level + 1]) + end -- local commitment = Hash.zero:iterated_merkle(consts.heights[level]) return builder:build(self.initial_hash) end diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua index 42b212c3a..b4e2637c7 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua @@ -36,19 +36,15 @@ end local function generate_proof(proof, root, height, include_index) if height == 0 then - if include_index == 0 then - proof.leaf = root - return - else - table.insert(proof, root) - end + proof.leaf = root + return end local new_height = height - 1 local ok, left, right = root:children() assert(ok) - if (include_index >> new_height) == 0 then + if (include_index >> new_height) & 1 == 0 then generate_proof(proof, left, new_height, include_index) table.insert(proof, right) else @@ -66,6 +62,8 @@ function MerkleTree:prove_leaf(index) height = self.log2size end + print(index, height, "P") + assert((index >> height) == 0) local proof = {} generate_proof(proof, self.root_hash, height, index) @@ -94,4 +92,20 @@ function MerkleTree:last() return old_right, array_reverse(proof) end +-- local Hash = require "cryptography.hash" +-- local MerkleBuilder = require "cryptography.merkle_builder" +-- local builder = MerkleBuilder:new() +-- builder:add(Hash.zero, 1 << 8) +-- local mt = builder:build() + +-- local i, p = mt:last((1 << 8) - 1) +-- local r = assert(i) +-- print(i) +-- for _, v in ipairs(p) do +-- print(v) +-- r = v:join(r) +-- end + +-- print("FINAL", r, mt.root_hash) + return MerkleTree diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index fdb892f1d..6c26425bf 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -7,11 +7,13 @@ print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" local machine_path = "offchain/program/simple-program" + local Player = require "player" local Client = require "blockchain.client" local Machine = require "computation.machine" -local initial_hash = Machine:new_from_path(machine_path):state().root_hash +local m = Machine:new_from_path(machine_path) +local initial_hash = m:state().root_hash local Blockchain = require "blockchain.node" local blockchain = Blockchain:new() @@ -29,6 +31,11 @@ end local p2 do local FakeCommitmentBuilder = require "computation.fake_commitment" + + -- m:run(m.start_cycle + 1) + -- local second_hash = m:state().root_hash + -- local builder = FakeCommitmentBuilder:new(initial_hash, second_hash) + local builder = FakeCommitmentBuilder:new(initial_hash) local client = Client:new(blockchain) p2 = Player:new(contract, client, builder) diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index a52dff70a..e7edc4859 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -66,13 +66,13 @@ library Commitment { bytes32[] calldata hashProof ) internal pure { uint64 treeHeight = ArbitrationConstants.height(level); - // Tree.Node expectedCommitment = getRootForLastLeaf( - // treeHeight, - // Machine.Hash.unwrap(finalState), - // hashProof - // ); + Tree.Node expectedCommitment = getRootForLastLeaf( + treeHeight, + Machine.Hash.unwrap(finalState), + hashProof + ); - // require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); + require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); } @@ -81,10 +81,9 @@ library Commitment { bytes32 leaf, bytes32[] calldata siblings ) internal pure returns (Tree.Node) { - uint nodesCount = treeHeight - 1; - assert(nodesCount == siblings.length); + assert(treeHeight == siblings.length); - for (uint i = 0; i < nodesCount; i++) { + for (uint i = 0; i < treeHeight; i++) { leaf = keccak256(abi.encodePacked(siblings[i], leaf)); } From 3e2f32072789cb36e489ae947f49a6926c7bce83 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Fri, 18 Aug 2023 22:06:35 +0200 Subject: [PATCH 17/25] feat(arbitration): integrate step --- .../lib/machine-solidity-step | 2 +- .../offchain/blockchain/client.lua | 2 +- .../offchain/computation/commitment.lua | 3 - .../offchain/computation/machine.lua | 69 ++++++++++++++++++- .../offchain/cryptography/merkle_tree.lua | 14 +--- .../offchain/entrypoint.lua | 9 ++- .../offchain/player.lua | 20 ++++-- .../offchain/utils/arithmetic.lua | 9 +++ .../src/CanonicalConstants.sol | 4 +- 9 files changed, 106 insertions(+), 26 deletions(-) diff --git a/onchain/permissionless-arbitration/lib/machine-solidity-step b/onchain/permissionless-arbitration/lib/machine-solidity-step index 83a5c0309..61f810ece 160000 --- a/onchain/permissionless-arbitration/lib/machine-solidity-step +++ b/onchain/permissionless-arbitration/lib/machine-solidity-step @@ -1 +1 @@ -Subproject commit 83a5c0309e70d7a30092460f0561baaa81b6e2a8 +Subproject commit 61f810ece4d9405cbabd7ff0018c18cf30fd43cb diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index fa4c026e7..6fdb0f47d 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -419,7 +419,7 @@ function Client:tx_win_leaf_match( tournament_address, commitment_one, commitment_two, left, right, proof ) local sig = - [[winLeafMatch((bytes32,bytes32),bytes32,bytes32)]] + [[winLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes)]] self:_send_tx( tournament_address, sig, diff --git a/onchain/permissionless-arbitration/offchain/computation/commitment.lua b/onchain/permissionless-arbitration/offchain/computation/commitment.lua index bb96b100c..37a51049f 100644 --- a/onchain/permissionless-arbitration/offchain/computation/commitment.lua +++ b/onchain/permissionless-arbitration/offchain/computation/commitment.lua @@ -82,8 +82,6 @@ end local function build_commitment(base_cycle, log2_stride, log2_stride_count, machine_path) local machine = Machine:new_from_path(machine_path) - print(log2_stride, log2_stride_count) - if log2_stride >= consts.log2_uarch_span then assert( log2_stride + log2_stride_count <= @@ -118,7 +116,6 @@ function CommitmentBuilder:build(base_cycle, level) local l = consts.levels - level + 1 local log2_stride, log2_stride_count = consts.log2step[l], consts.heights[l] - print(log2_stride, log2_stride_count) local _, commitment = build_commitment(base_cycle, log2_stride, log2_stride_count, self.machine_path) self.commitments[level][base_cycle] = commitment diff --git a/onchain/permissionless-arbitration/offchain/computation/machine.lua b/onchain/permissionless-arbitration/offchain/computation/machine.lua index afaff5835..2bccfd115 100644 --- a/onchain/permissionless-arbitration/offchain/computation/machine.lua +++ b/onchain/permissionless-arbitration/offchain/computation/machine.lua @@ -1,7 +1,7 @@ local Hash = require "cryptography.hash" local arithmetic = require "utils.arithmetic" -local consts = require "constants" local cartesi = require "cartesi" +local consts = require "constants" local ComputationState = {} ComputationState.__index = ComputationState @@ -39,7 +39,6 @@ end --- -- --- TODO Consider removing this abstraction local Machine = {} Machine.__index = Machine @@ -52,6 +51,7 @@ function Machine:new_from_path(path) assert(machine:read_uarch_cycle() == 0) local b = { + path = path, machine = machine, cycle = 0, ucycle = 0, @@ -104,4 +104,69 @@ function Machine:ureset() self.ucycle = 0 end +local keccak = require "cartesi".keccak + +local function hex_from_bin(bin) + assert(bin:len() == 32) + return "0x" .. (bin:gsub('.', function(c) + return string.format('%02x', string.byte(c)) + end)) +end + +local function ver(t, p, s) + local stride = p >> 3 + for k, v in ipairs(s) do + if (stride >> (k - 1)) % 2 == 0 then + t = keccak(t, v) + else + t = keccak(v, t) + end + end + + return t +end + + +function Machine:get_logs(path, cycle, ucycle) + local machine = Machine:new_from_path(path) + machine:run(cycle) + machine:run_uarch(ucycle) + + if ucycle == consts.uarch_span then + error "ureset, not implemented" + + machine:run_uarch(consts.uarch_span) + -- get reset-uarch logs + + return + end + + local logs = machine.machine:step_uarch { annotations = true, proofs = true } + + local encoded = {} + + for _, a in ipairs(logs.accesses) do + assert(a.log2_size == 3) + if a.type == "read" then + table.insert(encoded, a.read) + end + + table.insert(encoded, a.proof.target_hash) + + local siblings = arithmetic.array_reverse(a.proof.sibling_hashes) + for _, h in ipairs(siblings) do + table.insert(encoded, h) + end + + assert(ver(a.proof.target_hash, a.address, siblings) == a.proof.root_hash) + end + + local data = table.concat(encoded) + local hex_data = "0x" .. (data:gsub('.', function(c) + return string.format('%02x', string.byte(c)) + end)) + + return '"' .. hex_data .. '"' +end + return Machine diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua index b4e2637c7..13673e618 100644 --- a/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle_tree.lua @@ -1,3 +1,5 @@ +local arithmetic = require "utils.arithmetic" + local MerkleTree = {} MerkleTree.__index = MerkleTree @@ -62,22 +64,12 @@ function MerkleTree:prove_leaf(index) height = self.log2size end - print(index, height, "P") - assert((index >> height) == 0) local proof = {} generate_proof(proof, self.root_hash, height, index) return proof.leaf, proof end -local function array_reverse(x) - local n, m = #x, #x / 2 - for i = 1, m do - x[i], x[n - i + 1] = x[n - i + 1], x[i] - end - return x -end - function MerkleTree:last() local proof = {} local ok, left, right = self.root_hash:children() @@ -89,7 +81,7 @@ function MerkleTree:last() ok, left, right = right:children() end - return old_right, array_reverse(proof) + return old_right, arithmetic.array_reverse(proof) end -- local Hash = require "cryptography.hash" diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 6c26425bf..800cf3fc4 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -7,6 +7,11 @@ print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" local machine_path = "offchain/program/simple-program" +-- local Machine = require "computation.machine" +-- Machine:get_logs(machine_path, 0, 0) + +-- os.exit() + local Player = require "player" local Client = require "blockchain.client" @@ -25,7 +30,7 @@ do local CommitmentBuilder = require "computation.commitment" local builder = CommitmentBuilder:new(machine_path) local client = Client:new(blockchain) - p1 = Player:new(contract, client, builder) + p1 = Player:new(contract, client, builder, machine_path) end local p2 @@ -38,7 +43,7 @@ do local builder = FakeCommitmentBuilder:new(initial_hash) local client = Client:new(blockchain) - p2 = Player:new(contract, client, builder) + p2 = Player:new(contract, client, builder, machine_path) end local i = 0 diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua index 3591bcd19..c88298aa7 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player.lua @@ -1,11 +1,13 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers +local Machine = require "computation.machine" local Player = {} Player.__index = Player -function Player:new(root_tournament_address, client, commitment_builder) +function Player:new(root_tournament_address, client, commitment_builder, machine_path) local player = { + machine_path = machine_path, root_tournament = { base_big_cycle = 0, address = root_tournament_address, @@ -43,7 +45,7 @@ function Player:_react_tournament(tournament) local winner_final_state = self.client:root_tournament_winner(tournament.address) if winner_final_state[1] == "true" then print "TOURNAMENT FINISHED, HURRAYYY" - print("Final state: " .. winner_final_state[2]) + print("Final state: " .. winner_final_state[2]:hex_string()) return true end else @@ -103,19 +105,29 @@ function Player:_react_match(match, commitment) return end + print(string.format( + "Calculating access logs for step %s", + match.running_leaf + )) + + local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() + local ucycle = (match.running_leaf & constants.uarch_span):touinteger() + local logs = Machine:get_logs(self.machine_path, cycle, ucycle) + print(string.format( "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, commitment.root_hash )) - self.client:tx_win_leaf_match( + pcall(self.client.tx_win_leaf_match, + self.client, match.tournament.address, match.commitment_one, match.commitment_two, left, right, - "" + logs ) else local address = self.client:read_tournament_created( diff --git a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua index 18ce44c64..1fe1688b4 100644 --- a/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua +++ b/onchain/permissionless-arbitration/offchain/utils/arithmetic.lua @@ -49,6 +49,14 @@ local function semi_sum(a, b) return a + (b - a) // 2 end +local function array_reverse(x) + local n, m = #x, #x / 2 + for i = 1, m do + x[i], x[n - i + 1] = x[n - i + 1], x[i] + end + return x +end + return { max_uint = max_uint, max_uint64 = max_uint64, @@ -57,4 +65,5 @@ return { clz = clz, ctz = ctz, semi_sum = semi_sum, + array_reverse = array_reverse, } diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol index bd4f2a5f1..505a5e7af 100644 --- a/onchain/permissionless-arbitration/src/CanonicalConstants.sol +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -15,8 +15,8 @@ library ArbitrationConstants { // Time.Duration.wrap(60 * 60 * 24 * 7); // TODO // Dummy - Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(240); - Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(180); + Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(200); + Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(90); Time.Duration constant DISPUTE_TIMEOUT = Time.Duration.wrap( From 4874b06caad5b8095cd8b48218422f3b621589dc Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Sat, 19 Aug 2023 11:54:09 +0200 Subject: [PATCH 18/25] refactor(arbitration): improve code quality --- .../offchain/blockchain/client.lua | 10 ++- .../offchain/player.lua | 17 +++-- .../src/CanonicalConstants.sol | 4 +- .../permissionless-arbitration/src/Match.sol | 4 +- .../abstracts/NonLeafTournament.sol | 5 +- .../abstracts/NonRootTournament.sol | 26 +++---- .../tournament/abstracts/RootTournament.sol | 10 +-- .../src/tournament/abstracts/Tournament.sol | 74 +++++++++---------- .../test/MultiTournament.t.sol | 39 +++++----- .../test/Tournament.t.sol | 8 +- 10 files changed, 98 insertions(+), 99 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 6fdb0f47d..2db655520 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -314,17 +314,19 @@ function Client:match(address, match_id_hash) return ret end -function Client:tournament_winner(address) - local sig = "tournamentWinner()(bytes32)" +function Client:inner_tournament_winner(address) + local sig = "innerTournamentWinner()(bool,bytes32)" local ret = self:_call(address, sig, {}) + ret[2] = Hash:from_digest_hex(ret[2]) - return Hash:from_digest_hex(ret[1]) + return ret end function Client:root_tournament_winner(address) - local sig = "rootTournamentFinalState()(bool,bytes32)" + local sig = "arbitrationResult()(bool,bytes32,bytes32)" local ret = self:_call(address, sig, {}) ret[2] = Hash:from_digest_hex(ret[2]) + ret[3] = Hash:from_digest_hex(ret[3]) return ret end diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player.lua index c88298aa7..9963dd973 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player.lua @@ -45,14 +45,15 @@ function Player:_react_tournament(tournament) local winner_final_state = self.client:root_tournament_winner(tournament.address) if winner_final_state[1] == "true" then print "TOURNAMENT FINISHED, HURRAYYY" - print("Final state: " .. winner_final_state[2]:hex_string()) + print("Winner commitment: " .. winner_final_state[2]:hex_string()) + print("Final state: " .. winner_final_state[3]:hex_string()) return true end else - local tournament_winner = self.client:tournament_winner(tournament.address) - if not tournament_winner:is_zero() then + local tournament_winner = self.client:inner_tournament_winner(tournament.address) + if tournament_winner[1] == "true" then local old_commitment = self.commitments[tournament.parent.address] - if tournament_winner ~= old_commitment.root_hash then + if tournament_winner[2] ~= old_commitment.root_hash then print "player lost tournament" self.has_lost = true return @@ -120,7 +121,7 @@ function Player:_react_match(match, commitment) match.tournament.level, commitment.root_hash )) - pcall(self.client.tx_win_leaf_match, + local ok, e = pcall(self.client.tx_win_leaf_match, self.client, match.tournament.address, match.commitment_one, @@ -129,6 +130,12 @@ function Player:_react_match(match, commitment) right, logs ) + if not ok then + print(string.format( + "win leaf match reverted: %s", + e + )) + end else local address = self.client:read_tournament_created( match.tournament.address, diff --git a/onchain/permissionless-arbitration/src/CanonicalConstants.sol b/onchain/permissionless-arbitration/src/CanonicalConstants.sol index 505a5e7af..c06a26f3a 100644 --- a/onchain/permissionless-arbitration/src/CanonicalConstants.sol +++ b/onchain/permissionless-arbitration/src/CanonicalConstants.sol @@ -15,8 +15,8 @@ library ArbitrationConstants { // Time.Duration.wrap(60 * 60 * 24 * 7); // TODO // Dummy - Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(200); - Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(90); + Time.Duration constant VALIDATOR_EFFORT = Time.Duration.wrap(0); + Time.Duration constant CENSORSHIP_TOLERANCE = Time.Duration.wrap(210); Time.Duration constant DISPUTE_TIMEOUT = Time.Duration.wrap( diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index 09a19b108..17976d185 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -303,8 +303,8 @@ library Match { state.leftNode = newLeftNode; state.rightNode = newRightNode; - state.runningLeafPosition += 1 << state.currentHeight; state.currentHeight--; + state.runningLeafPosition += 1 << state.currentHeight; } function _setDivergenceOnLeftLeaf( @@ -336,8 +336,8 @@ library Match { { assert(state.currentHeight == 1); state.leftNode = rightLeaf; - state.runningLeafPosition += 1; state.currentHeight = 0; + state.runningLeafPosition += 1; if (state.height() % 2 == 0) { finalStateOne = state.rightNode.toMachineHash(); diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index b5b59c0cb..6e095585d 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -115,7 +115,8 @@ abstract contract NonLeafTournament is Tournament { _matchState.requireExist(); _matchState.requireIsFinished(); - Tree.Node _winner = _childTournament.tournamentWinner(); + (bool finished, Tree.Node _winner) = _childTournament.innerTournamentWinner(); + require(finished, "child tournament is not finished"); _winner.requireExist(); Tree.Node _commitmentRoot = _leftNode.join(_rightNode); @@ -190,6 +191,4 @@ abstract contract NonLeafTournament is Tournament { return NonRootTournament(address(_tournament)); } - - } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol index 45d1747e6..744887e8e 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonRootTournament.sol @@ -45,20 +45,24 @@ abstract contract NonRootTournament is Tournament { } /// @notice get the dangling commitment at current level and then retrieve the winner commitment - function tournamentWinner() external view override returns (Tree.Node) { - Tree.Node _danglingCommitment = _tournamentWinner(); - - if (_danglingCommitment.isZero()) { - return Tree.ZERO_NODE; + function innerTournamentWinner() external view returns (bool, Tree.Node) { + if (!isFinished()) { + return (false, Tree.ZERO_NODE); } + ( + bool _hasDanglingCommitment, + Tree.Node _danglingCommitment + ) = hasDanglingCommitment(); + assert(_hasDanglingCommitment); + Machine.Hash _finalState = finalStates[_danglingCommitment]; if (_finalState.eq(contestedFinalStateOne)) { - return contestedCommitmentOne; + return (true, contestedCommitmentOne); } else { assert(_finalState.eq(contestedFinalStateTwo)); - return contestedCommitmentTwo; + return (true, contestedCommitmentTwo); } } @@ -72,12 +76,6 @@ abstract contract NonRootTournament is Tournament { function validContestedFinalState( Machine.Hash _finalState ) internal view override returns (bool) { - if (contestedFinalStateOne.eq(_finalState)) { - return true; - } else if (contestedFinalStateTwo.eq(_finalState)) { - return true; - } else { - return false; - } + return contestedFinalStateOne.eq(_finalState) || contestedFinalStateTwo.eq(_finalState); } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol index 0b0836ce7..ae7a48b95 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/RootTournament.sol @@ -15,10 +15,6 @@ abstract contract RootTournament is Tournament { Machine.Hash _initialHash ) Tournament(_initialHash, ArbitrationConstants.CENSORSHIP_TOLERANCE, 0, 0) {} - function tournamentWinner() external view override returns (Tree.Node) { - return _tournamentWinner(); - } - function updateParentTournamentDelay( Time.Instant _delay ) internal override { @@ -32,9 +28,9 @@ abstract contract RootTournament is Tournament { return true; } - function rootTournamentFinalState() external view returns (bool, Machine.Hash) { + function arbitrationResult() external view returns (bool, Tree.Node, Machine.Hash) { if (!isFinished()) { - return (false, Machine.ZERO_STATE); + return (false, Tree.ZERO_NODE, Machine.ZERO_STATE); } (bool _hasDanglingCommitment, Tree.Node _danglingCommitment) = @@ -42,6 +38,6 @@ abstract contract RootTournament is Tournament { assert(_hasDanglingCommitment); Machine.Hash _finalState = finalStates[_danglingCommitment]; - return (true, _finalState); + return (true, _danglingCommitment, _finalState); } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index d4ca1bf4b..bd75fcff9 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -110,6 +110,19 @@ abstract contract Tournament { } } + + // + // Virtual Methods + // + + function updateParentTournamentDelay(Time.Instant _delay) internal virtual; + + /// @return bool if commitment with _finalState is allowed to join the tournament + function validContestedFinalState( + Machine.Hash _finalState + ) internal view virtual returns (bool); + + // // Methods // @@ -209,22 +222,6 @@ abstract contract Tournament { delete matches[_matchId.hashFromId()]; } - function _tournamentWinner() internal view returns (Tree.Node) { - if (!isFinished()) { - return Tree.ZERO_NODE; - } - - ( - bool _hasDanglingCommitment, - Tree.Node _danglingCommitment - ) = hasDanglingCommitment(); - assert(_hasDanglingCommitment); - - return _danglingCommitment; - } - - /// @return _winner commitment of the tournament - function tournamentWinner() external view virtual returns (Tree.Node); // // View methods @@ -272,6 +269,15 @@ abstract contract Tournament { // Helper functions // + function requireValidContestedFinalState( + Machine.Hash _finalState + ) internal view { + require( + validContestedFinalState(_finalState), + "tournament doesn't have contested final state" + ); + } + function hasDanglingCommitment() internal view @@ -292,23 +298,13 @@ abstract contract Tournament { danglingCommitment = Tree.ZERO_NODE; } - function updateParentTournamentDelay(Time.Instant _delay) internal virtual; - - function setMaximumDelay(Time.Instant _delay) internal returns (bool) { - if (_delay.gt(maximumEnforceableDelay)) { - maximumEnforceableDelay = _delay; - return true; - } else { - return false; - } - } - function pairCommitment( Tree.Node _rootHash, Clock.State memory _newClock, Tree.Node _leftNode, Tree.Node _rightNode ) internal { + assert(_leftNode.join(_rightNode).eq(_rootHash)); ( bool _hasDanglingCommitment, Tree.Node _danglingCommitment @@ -345,19 +341,10 @@ abstract contract Tournament { } } - /// @return bool if _fianlState is allowed to join the tournament - function validContestedFinalState( - Machine.Hash _fianlState - ) internal view virtual returns (bool); - function requireValidContestedFinalState( - Machine.Hash _finalState - ) internal view { - require( - validContestedFinalState(_finalState), - "tournament doesn't have contested final state" - ); - } + // + // Clock methods + // /// @return bool if the tournament is still open to join function isClosed() internal view returns (bool) { @@ -375,4 +362,13 @@ abstract contract Tournament { function isFinished() internal view returns (bool) { return Time.currentTime().gt(maximumEnforceableDelay); } + + function setMaximumDelay(Time.Instant _delay) internal returns (bool) { + if (_delay.gt(maximumEnforceableDelay)) { + maximumEnforceableDelay = _delay; + return true; + } else { + return false; + } + } } diff --git a/onchain/permissionless-arbitration/test/MultiTournament.t.sol b/onchain/permissionless-arbitration/test/MultiTournament.t.sol index 24bcae5d0..4f5afeb3f 100644 --- a/onchain/permissionless-arbitration/test/MultiTournament.t.sol +++ b/onchain/permissionless-arbitration/test/MultiTournament.t.sol @@ -76,9 +76,8 @@ contract MultiTournamentTest is Test { ); // no winner before tournament finished - Tree.Node _winner = topTournament.tournamentWinner(); - (bool _finished, Machine.Hash _finalState) = topTournament - .rootTournamentFinalState(); + (bool _finished, Tree.Node _winner, Machine.Hash _finalState) = topTournament + .arbitrationResult(); assertTrue(_winner.isZero(), "winner should be zero node"); assertFalse(_finished, "tournament shouldn't be finished"); @@ -98,8 +97,7 @@ contract MultiTournamentTest is Test { Time.Duration.unwrap(ArbitrationConstants.CENSORSHIP_TOLERANCE); vm.warp(_tournamentFinish); - _winner = topTournament.tournamentWinner(); - (_finished, _finalState) = topTournament.rootTournamentFinalState(); + (_finished, _winner, _finalState) = topTournament.arbitrationResult(); assertTrue( _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), @@ -119,7 +117,7 @@ contract MultiTournamentTest is Test { // no dangling commitment available, should revert vm.warp(_tournamentFinishWithMatch); vm.expectRevert(); - _winner = topTournament.tournamentWinner(); + topTournament.arbitrationResult(); } function testInner() public { @@ -239,8 +237,8 @@ contract MultiTournamentTest is Test { address(bytes20(bytes32(_entries[0].data) << (12 * 8))) ); - Tree.Node _winner = middleTournament.tournamentWinner(); - assertTrue(_winner.isZero(), "winner should be zero node"); + (bool _finished, Tree.Node _winner) = middleTournament.innerTournamentWinner(); + assertFalse(_finished, "winner should be zero node"); // player 0 should win after fast forward time to inner tournament finishes uint256 _t = block.timestamp; @@ -251,26 +249,28 @@ contract MultiTournamentTest is Test { Util.joinMiddleTournament(playerNodes, middleTournament, 0, 1); vm.warp(_rootTournamentFinish - 1); - _winner = middleTournament.tournamentWinner(); + (_finished, _winner) = middleTournament.innerTournamentWinner(); topTournament.winInnerMatch( middleTournament, playerNodes[0][ArbitrationConstants.height(0) - 1], playerNodes[0][ArbitrationConstants.height(0) - 1] ); + { vm.warp(_rootTournamentFinish + 1); - (bool _finished, Machine.Hash _finalState) = topTournament - .rootTournamentFinalState(); + (bool _finishedTop, Tree.Node _commitment, Machine.Hash _finalState) = topTournament + .arbitrationResult(); assertTrue( - _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), + _commitment.eq(playerNodes[0][ArbitrationConstants.height(0)]), "winner should be player 0" ); - assertTrue(_finished, "tournament should be finished"); + assertTrue(_finishedTop, "tournament should be finished"); assertTrue( _finalState.eq(Tree.ZERO_NODE.toMachineHash()), "final state should be zero" ); + } //create another tournament for other test topTournament = Util.initializePlayer0Tournament( @@ -323,7 +323,7 @@ contract MultiTournamentTest is Test { address(bytes20(bytes32(_entries[0].data) << (12 * 8))) ); - _winner = middleTournament.tournamentWinner(); + (_finished, _winner) = middleTournament.innerTournamentWinner(); assertTrue(_winner.isZero(), "winner should be zero node"); _t = block.timestamp; @@ -358,24 +358,27 @@ contract MultiTournamentTest is Test { ); vm.warp(_middleTournamentFinish); - _winner = middleTournament.tournamentWinner(); + (_finished, _winner) = middleTournament.innerTournamentWinner(); topTournament.winInnerMatch( middleTournament, playerNodes[1][ArbitrationConstants.height(0) - 1], playerNodes[1][ArbitrationConstants.height(0) - 1] ); + { vm.warp(_rootTournamentFinish); - (_finished, _finalState) = topTournament.rootTournamentFinalState(); + (bool _finishedTop, Tree.Node _commitment, Machine.Hash _finalState) = topTournament + .arbitrationResult(); assertTrue( - _winner.eq(playerNodes[1][ArbitrationConstants.height(0)]), + _commitment.eq(playerNodes[1][ArbitrationConstants.height(0)]), "winner should be player 1" ); - assertTrue(_finished, "tournament should be finished"); + assertTrue(_finishedTop, "tournament should be finished"); assertTrue( _finalState.eq(Util.ONE_NODE.toMachineHash()), "final state should be 1" ); + } } } diff --git a/onchain/permissionless-arbitration/test/Tournament.t.sol b/onchain/permissionless-arbitration/test/Tournament.t.sol index 98c4320a1..2bf3d5656 100644 --- a/onchain/permissionless-arbitration/test/Tournament.t.sol +++ b/onchain/permissionless-arbitration/test/Tournament.t.sol @@ -134,9 +134,8 @@ contract TournamentTest is Test { ); vm.warp(_tournamentFinishWithMatch); - Tree.Node _winner = topTournament.tournamentWinner(); - (bool _finished, Machine.Hash _finalState) = topTournament - .rootTournamentFinalState(); + (bool _finished, Tree.Node _winner, Machine.Hash _finalState) = topTournament + .arbitrationResult(); assertTrue( _winner.eq(playerNodes[1][ArbitrationConstants.height(0)]), @@ -191,8 +190,7 @@ contract TournamentTest is Test { ); vm.warp(_tournamentFinishWithMatch); - _winner = topTournament.tournamentWinner(); - (_finished, _finalState) = topTournament.rootTournamentFinalState(); + (_finished, _winner, _finalState) = topTournament.arbitrationResult(); assertTrue( _winner.eq(playerNodes[0][ArbitrationConstants.height(0)]), From 96393c9ac134bff9977fb833b6132b751ebad7e8 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Mon, 21 Aug 2023 20:52:38 +0200 Subject: [PATCH 19/25] chore(arbitration): update machine version --- onchain/permissionless-arbitration/Dockerfile | 7 +++++-- onchain/permissionless-arbitration/README.md | 7 +++++++ onchain/permissionless-arbitration/offchain/README.md | 7 ------- .../offchain/program/gen_machine_simple.sh | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 onchain/permissionless-arbitration/README.md delete mode 100644 onchain/permissionless-arbitration/offchain/README.md diff --git a/onchain/permissionless-arbitration/Dockerfile b/onchain/permissionless-arbitration/Dockerfile index 8f00fc4ef..a0974c1f1 100644 --- a/onchain/permissionless-arbitration/Dockerfile +++ b/onchain/permissionless-arbitration/Dockerfile @@ -1,5 +1,7 @@ -FROM --platform=linux/amd64 diegonehab/machine-emulator -RUN apt-get -y update; apt-get -y install curl +FROM cartesi/machine-emulator:main + +USER 0 +RUN apt-get -y update; apt-get -y install curl git RUN curl -sSL https://github.com/foundry-rs/foundry/releases/download/nightly/foundry_nightly_linux_$(dpkg --print-architecture).tar.gz | \ tar -zx -C /usr/local/bin @@ -7,6 +9,7 @@ ADD foundry.toml ./project/ ADD lib ./project/lib/ ADD src ./project/src/ WORKDIR "./project" +RUN forge --version RUN forge build ADD ./offchain/ ./offchain/ diff --git a/onchain/permissionless-arbitration/README.md b/onchain/permissionless-arbitration/README.md new file mode 100644 index 000000000..dc8bdc48d --- /dev/null +++ b/onchain/permissionless-arbitration/README.md @@ -0,0 +1,7 @@ +# Permissionless Arbitration (NxN) Lua prototype Node + +## Run example + +``` +docker build -t nxn_playground:latest . && docker run --rm nxn_playground:latest +``` diff --git a/onchain/permissionless-arbitration/offchain/README.md b/onchain/permissionless-arbitration/offchain/README.md deleted file mode 100644 index 18927788c..000000000 --- a/onchain/permissionless-arbitration/offchain/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Permissionless Arbitration (NxN) Lua prototype Node - -## Run example - -``` -docker build -t nxn_playground:latest . && docker run --platform linux/amd64 --rm nxn_playground:latest -``` diff --git a/onchain/permissionless-arbitration/offchain/program/gen_machine_simple.sh b/onchain/permissionless-arbitration/offchain/program/gen_machine_simple.sh index e5e39b38f..4b33b89ab 100755 --- a/onchain/permissionless-arbitration/offchain/program/gen_machine_simple.sh +++ b/onchain/permissionless-arbitration/offchain/program/gen_machine_simple.sh @@ -3,7 +3,7 @@ cartesi-machine \ --no-root-flash-drive \ --rom-image="./bins/bootstrap.bin" \ --ram-image="./bins/rv64ui-p-addi.bin" \ - --uarch-ram-image="/opt/cartesi/share/images/uarch-ram.bin" \ + --uarch-ram-image="/usr/share/cartesi-machine/uarch/uarch-ram.bin" \ --uarch-ram-length=0x1000000 \ --max-mcycle=0 \ --store="simple-program" From 591947e59622cd453a01341fa152685056c4860f Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:21:48 +0800 Subject: [PATCH 20/25] refactor(arbitration): split players into different Lua VMs --- onchain/permissionless-arbitration/Dockerfile | 2 +- .../offchain/blockchain/client.lua | 15 +- .../offchain/blockchain/constants.lua | 30 ++++ .../offchain/blockchain/node.lua | 56 ++---- .../offchain/entrypoint.lua | 164 +++++++----------- .../offchain/player/dishonest_player.lua | 26 +++ .../offchain/player/honest_player.lua | 23 +++ .../honest_strategy.lua} | 42 +++-- .../offchain/utils/color.lua | 133 ++++++++++++++ .../offchain/utils/log.lua | 35 ++++ .../offchain/utils/time.lua | 8 + 11 files changed, 362 insertions(+), 172 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/constants.lua create mode 100755 onchain/permissionless-arbitration/offchain/player/dishonest_player.lua create mode 100755 onchain/permissionless-arbitration/offchain/player/honest_player.lua rename onchain/permissionless-arbitration/offchain/{player.lua => player/honest_strategy.lua} (87%) create mode 100644 onchain/permissionless-arbitration/offchain/utils/color.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/log.lua create mode 100644 onchain/permissionless-arbitration/offchain/utils/time.lua diff --git a/onchain/permissionless-arbitration/Dockerfile b/onchain/permissionless-arbitration/Dockerfile index a0974c1f1..18487c523 100644 --- a/onchain/permissionless-arbitration/Dockerfile +++ b/onchain/permissionless-arbitration/Dockerfile @@ -1,7 +1,7 @@ FROM cartesi/machine-emulator:main USER 0 -RUN apt-get -y update; apt-get -y install curl git +RUN apt-get -y update; apt-get -y install curl git; apt-get install -y procps RUN curl -sSL https://github.com/foundry-rs/foundry/releases/download/nightly/foundry_nightly_linux_$(dpkg --print-architecture).tar.gz | \ tar -zx -C /usr/local/bin diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 2db655520..5b33e8c8b 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -142,11 +142,12 @@ end local Client = {} Client.__index = Client -function Client:new(blockchain) +function Client:new(account_index) + local blockchain_data = require "blockchain.constants" + local client = { - endpoint = blockchain.endpoint, - account = blockchain:new_account(), - blockchain = blockchain, + endpoint = blockchain_data.endpoint, + pk = blockchain_data.pks[account_index], } setmetatable(client, self) @@ -193,8 +194,6 @@ function Client:_read_logs(tournament_address, sig, topics, data_sig) end local ret = parse_logs(logs, data_sig) - - self.blockchain:read_to("eth_getLogs") return ret end @@ -234,7 +233,6 @@ function Client:_call(address, sig, args) end handle:close() - self.blockchain:read_to("eth_call") return ret end @@ -348,7 +346,7 @@ function Client:_send_tx(tournament_address, sig, args) local cmd = string.format( cast_send_template, - self.account.pk, + self.pk, self.endpoint, tournament_address, sig, @@ -364,7 +362,6 @@ function Client:_send_tx(tournament_address, sig, args) error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) end handle:close() - self.blockchain:read_to("eth_sendRawTransaction") end function Client:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua new file mode 100644 index 000000000..75a963629 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua @@ -0,0 +1,30 @@ +-- contains default 10 accounts of anvil test node, and deployed tournament contract address +local constants = { + endpoint = "http://127.0.0.1:8545", + addresses = { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + }, + pks = { + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + }, +} + +return constants diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index 3ca56e131..93aaf98cb 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -11,7 +11,7 @@ local function start_blockchain(account_num) account_num = account_num or default_account_number print(string.format("Starting blockchain with %d accounts...", account_num)) - local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d"]], account_num) + local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d > anvil.log 2>&1"]], account_num) local reader = io.popen(cmd) assert(reader, "`popen` returned nil reader") @@ -29,37 +29,9 @@ local function start_blockchain(account_num) return handle end -local function capture_blockchain_data(reader, account_num) - account_num = account_num or default_account_number - local str - - local addresses = {} - repeat - str = reader:read(); - local _, _, address = str:find [[%(%d+%) ("0x%x+")]] - if address then - table.insert(addresses, address) - end - until str:find("Private Keys") - assert(#addresses == account_num) - - local pks = {} - repeat - str = reader:read(); - local _, _, pk = str:find("%(%d+%) (0x%x+)") - if pk then - table.insert(pks, pk) - end - until str:find("Wallet") - assert(#pks == account_num) - - local endpoint - repeat - str = reader:read(); - _, _, endpoint = str:find("Listening on ([%w%p]+)") - until endpoint - - return { address = addresses, pk = pks }, endpoint +local function capture_blockchain_data() + local blockchain_data = require "blockchain.constants" + return { address = blockchain_data.addresses, pk = blockchain_data.pks }, blockchain_data.endpoint end @@ -78,7 +50,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, sl_factory_address = handle_sl:read("*a"):find("Deployed to: (0x%x+)") assert(sl_factory_address, "deployment failed, factory_address is nil") - print("Single Level factory deployed at", sl_factory_address) + print("Single Level factory deployed at: " .. sl_factory_address) handle_sl:close() -- @@ -95,7 +67,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, top_factory_address = handle_top:read("*a"):find("Deployed to: (0x%x+)") assert(top_factory_address, "deployment failed, factory_address is nil") - print("Top factory deployed at", top_factory_address) + print("Top factory deployed at: " .. top_factory_address) handle_top:close() -- @@ -112,7 +84,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, mid_factory_address = handle_mid:read("*a"):find("Deployed to: (0x%x+)") assert(mid_factory_address, "deployment failed, factory_address is nil") - print("Middle factory deployed at", mid_factory_address) + print("Middle factory deployed at: " .. mid_factory_address) handle_mid:close() -- @@ -129,7 +101,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, bot_factory_address = handle_bot:read("*a"):find("Deployed to: (0x%x+)") assert(bot_factory_address, "deployment failed, factory_address is nil") - print("Bottom factory deployed at", bot_factory_address) + print("Bottom factory deployed at: " .. bot_factory_address) handle_bot:close() @@ -147,7 +119,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, tournament_factory_address = handle_tournament:read("*a"):find("Deployed to: (0x%x+)") assert(tournament_factory_address, "deployment failed, factory_address is nil") - print("tournament factory deployed at", tournament_factory_address) + print("tournament factory deployed at: " .. tournament_factory_address) handle_tournament:close() @@ -166,7 +138,7 @@ local function deploy_contracts(endpoint, deployer, initial_hash) local _, _, a = handle_root:read("*a"):find [["data":"0x000000000000000000000000(%x+)"]] local address = "0x" .. a assert(address, "deployment failed, address is nil") - print("Contract deployed at", address) + print("Contract deployed at: " .. address) handle_root:close() return address @@ -179,12 +151,12 @@ function Blockchain:new(account_num) local blockchain = {} local handle = start_blockchain(account_num) - local accounts, endpoint = capture_blockchain_data(handle.reader, account_num) + local accounts, endpoint = capture_blockchain_data() blockchain._handle = handle blockchain._accounts = accounts blockchain._current_account = 1 - blockchain.endpoint = "http://" .. endpoint + blockchain.endpoint = endpoint setmetatable(blockchain, self) return blockchain @@ -211,10 +183,6 @@ function Blockchain:deploy_contract(initial_hash, deployer) return address, deployer end -function Blockchain:read_to(p) - repeat until self._handle.reader:read():find(p) -end - -- local bc = Blockchain:new(100) -- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -- bc:deploy_contract(initial_hash) diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 800cf3fc4..6b220831f 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -3,120 +3,86 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -print "Hello, world!" -os.execute "cd offchain/program && ./gen_machine_simple.sh" local machine_path = "offchain/program/simple-program" +local ps_template = [[ps %s | grep defunct | wc -l]] + +local log = require 'utils.log' +local Blockchain = require "blockchain.node" +local Machine = require "computation.machine" --- local Machine = require "computation.machine" --- Machine:get_logs(machine_path, 0, 0) +local function is_zombie(pid) + local reader = io.popen(string.format(ps_template, pid)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end --- os.exit() +local function stop_players(pid_reader) + for pid, reader in pairs(pid_reader) do + print(string.format("Stopping player with pid %s...", pid)) + os.execute(string.format("kill -15 %s", pid)) + reader:close() + print "Player stopped" + end +end -local Player = require "player" -local Client = require "blockchain.client" +print "Hello, world!" +os.execute "cd offchain/program && ./gen_machine_simple.sh" -local Machine = require "computation.machine" local m = Machine:new_from_path(machine_path) local initial_hash = m:state().root_hash - -local Blockchain = require "blockchain.node" local blockchain = Blockchain:new() local contract = blockchain:deploy_contract(initial_hash) - -local p1 -do - local CommitmentBuilder = require "computation.commitment" - local builder = CommitmentBuilder:new(machine_path) - local client = Client:new(blockchain) - p1 = Player:new(contract, client, builder, machine_path) +-- add more player instances here +local cmds = { + string.format([[sh -c "echo $$ ; exec ./offchain/player/honest_player.lua %d %s %s | tee honest.log"]], 1, contract, machine_path), + string.format([[sh -c "echo $$ ; exec ./offchain/player/dishonest_player.lua %d %s %s %s | tee dishonest.log"]], 2, contract, machine_path, initial_hash) +} +local pid_reader = {} +local pid_player = {} + +for i, cmd in ipairs(cmds) do + local reader = io.popen(cmd) + local pid = reader:read() + pid_reader[pid] = reader + pid_player[pid] = i end -local p2 -do - local FakeCommitmentBuilder = require "computation.fake_commitment" - - -- m:run(m.start_cycle + 1) - -- local second_hash = m:state().root_hash - -- local builder = FakeCommitmentBuilder:new(initial_hash, second_hash) - - local builder = FakeCommitmentBuilder:new(initial_hash) - local client = Client:new(blockchain) - p2 = Player:new(contract, client, builder, machine_path) -end +-- gracefully end children processes +setmetatable(pid_reader, { + __gc = function(t) + stop_players(t) + end +}) -local i = 0 +local no_active_players = 0 while true do - print(string.format("\n\n### ROUND %d ###\n", i)) - - print "Player 1 react" - if p1:react() then break end - - print "" - - print "Player 2 react" - if p2:react() then break end - - i = i + 1 + local last_ts = [[01/01/2000 00:00:00]] + local players = 0 + + for pid, reader in pairs(pid_reader) do + players = players + 1 + if is_zombie(pid) then + log.log(pid_player[pid], string.format("player process %s is dead", pid)) + reader:close() + pid_reader[pid] = nil + else + last_ts = log.log_to_ts(reader, last_ts) + end + end + + if players == 0 then + no_active_players = no_active_players + 1 + else + no_active_players = 0 + end + + -- if no active player processes for 10 consecutive iterations, break loop + if no_active_players == 10 then break end + + -- TODO: if all players are idle, advance anvil end - - - - - - - - - - --- os.execute "jsonrpc-remote-cartesi-machine --server-address=localhost:8080 &" --- os.execute "sleep 2" - --- require "cryptography.merkle_builder" --- require "computation.commitment" --- require "computation.machine_test" - --- local Blockchain = require "blockchain.node" - --- local bc = Blockchain:new(100) --- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" --- bc:deploy_contract(initial_hash) - - --- local utils = require "utils" --- local cartesi = {} --- cartesi.rpc = require"cartesi.grpc" - --- local remote = cartesi.rpc.stub("localhost:8080", "localhost:8081") --- local v = assert(remote.get_version()) --- print(string.format("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch)) - --- local machine = remote.machine("program/simple-program") --- print("cycles", machine:read_mcycle(), machine:read_uarch_cycle()) --- machine:snapshot() --- machine:snapshot() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) --- machine:rollback() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) --- machine:rollback() - --- print(utils.hex_from_bin(machine:get_root_hash())) --- machine:run(1000) --- print(machine:read_iflags_H(), utils.hex_from_bin(machine:get_root_hash())) - - - - --- machine:read_mcycle() - - - print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua new file mode 100755 index 000000000..ca6bbab84 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -0,0 +1,26 @@ +#!/usr/bin/lua +package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.path = package.path .. ";./offchain/?.lua" +package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" + +local Player = require "player.honest_strategy" +local Client = require "blockchain.client" +local Hash = require "cryptography.hash" + +local time = require "utils.time" + +local player_index = tonumber(arg[1]) +local tournament = arg[2] +local machine_path = arg[3] +local initial_hash = Hash:from_digest_hex(arg[4]) +local p +do + local FakeCommitmentBuilder = require "computation.fake_commitment" + local builder = FakeCommitmentBuilder:new(initial_hash) + p = Player:new(tournament, player_index, builder, machine_path) +end + +while true do + if p:react() then break end + time.sleep(1) +end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_player.lua b/onchain/permissionless-arbitration/offchain/player/honest_player.lua new file mode 100755 index 000000000..c9c592f3c --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -0,0 +1,23 @@ +#!/usr/bin/lua +package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" +package.path = package.path .. ";./offchain/?.lua" +package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" + +local Player = require "player.honest_strategy" + +local time = require "utils.time" + +local player_index = tonumber(arg[1]) +local tournament = arg[2] +local machine_path = arg[3] +local p +do + local CommitmentBuilder = require "computation.commitment" + local builder = CommitmentBuilder:new(machine_path) + p = Player:new(tournament, player_index, builder, machine_path) +end + +while true do + if p:react() then break end + time.sleep(1) +end diff --git a/onchain/permissionless-arbitration/offchain/player.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua similarity index 87% rename from onchain/permissionless-arbitration/offchain/player.lua rename to onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index 9963dd973..fe676b25e 100644 --- a/onchain/permissionless-arbitration/offchain/player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -1,11 +1,14 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers +local log = require 'utils.log' + local Machine = require "computation.machine" +local Client = require "blockchain.client" local Player = {} Player.__index = Player -function Player:new(root_tournament_address, client, commitment_builder, machine_path) +function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) local player = { machine_path = machine_path, root_tournament = { @@ -14,10 +17,11 @@ function Player:new(root_tournament_address, client, commitment_builder, machine level = constants.levels, parent = false, }, - client = client, + client = Client:new(player_index), commitment_builder = commitment_builder, commitments = {}, - called_win = {} + called_win = {}, + player_index = player_index } setmetatable(player, self) @@ -26,7 +30,7 @@ end function Player:react() if self.has_lost then - return + return true end return self:_react_tournament(self.root_tournament) end @@ -44,9 +48,9 @@ function Player:_react_tournament(tournament) if not tournament.parent then local winner_final_state = self.client:root_tournament_winner(tournament.address) if winner_final_state[1] == "true" then - print "TOURNAMENT FINISHED, HURRAYYY" - print("Winner commitment: " .. winner_final_state[2]:hex_string()) - print("Final state: " .. winner_final_state[3]:hex_string()) + log.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") + log.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) + log.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) return true end else @@ -54,19 +58,19 @@ function Player:_react_tournament(tournament) if tournament_winner[1] == "true" then local old_commitment = self.commitments[tournament.parent.address] if tournament_winner[2] ~= old_commitment.root_hash then - print "player lost tournament" + log.log(self.player_index, "player lost tournament") self.has_lost = true return end if self.called_win[tournament.address] then - print "player already called winInnerMatch" + log.log(self.player_index, "player already called winInnerMatch") return else self.called_win[tournament.address] = true end - print(string.format( + log.log(self.player_index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, @@ -90,7 +94,7 @@ end function Player:_react_match(match, commitment) -- TODO call timeout if needed - print("HEIGHT", match.current_height) + log.log(self.player_index, "HEIGHT: " .. match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then @@ -102,11 +106,11 @@ function Player:_react_match(match, commitment) if finished then local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - print("DELAY", delay - os.time()) + log.log(self.player_index, "DELAY", delay - os.time()) return end - print(string.format( + log.log(self.player_index, string.format( "Calculating access logs for step %s", match.running_leaf )) @@ -115,7 +119,7 @@ function Player:_react_match(match, commitment) local ucycle = (match.running_leaf & constants.uarch_span):touinteger() local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - print(string.format( + log.log(self.player_index, string.format( "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -131,7 +135,7 @@ function Player:_react_match(match, commitment) logs ) if not ok then - print(string.format( + log.log(self.player_index, string.format( "win leaf match reverted: %s", e )) @@ -163,7 +167,7 @@ function Player:_react_match(match, commitment) end if match.tournament.level == 1 then - print(string.format( + log.log(self.player_index, string.format( "seal leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -179,7 +183,7 @@ function Player:_react_match(match, commitment) proof ) else - print(string.format( + log.log(self.player_index, string.format( "seal inner match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -226,7 +230,7 @@ function Player:_react_match(match, commitment) assert(f) end - print(string.format( + log.log(self.player_index, string.format( "advance match with current height %d in tournament %s of level %d for commitment %s", match.current_height, match.tournament.address, @@ -280,7 +284,7 @@ function Player:_join_tournament_if_needed(tournament, commitment) assert(f) local last, proof = commitment:last() - print(string.format( + log.log(self.player_index, string.format( "join tournament %s of level %d with commitment %s", tournament.address, tournament.level, diff --git a/onchain/permissionless-arbitration/offchain/utils/color.lua b/onchain/permissionless-arbitration/offchain/utils/color.lua new file mode 100644 index 000000000..c188228c0 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/color.lua @@ -0,0 +1,133 @@ +-- color.lua + +-------------------------------------------------------------------------------- + +-- A super-simple way to make colored text output in Lua. +-- To use, simply print out things from this module, then print out some text. +-- +-- Example: +-- print(color.bg.green .. color.fg.RED .. "This is bright red on green") +-- print(color.invert .. "This is inverted..." .. color.reset .. " And this isn't.") +-- print(color.fg(0xDE) .. color.bg(0xEE) .. "You can use xterm-256 colors too!" .. color.reset) +-- print("And also " .. color.bold .. "BOLD" .. color.normal .. " if you want.") +-- print(color.bold .. color.fg.BLUE .. color.bg.blue .. "Miss your " .. color.fg.RED .. "C-64" .. color.fg.BLUE .. "?" .. color.reset) +-- +-- You can see all these examples in action by calling color.test() +-- +-- Can't pick a good color scheme? Look at a handy chart: +-- print(color.chart()) +-- +-- If you want to add anything to this, check out the Wikipedia page on ANSI control codes: +-- http://en.wikipedia.org/wiki/ANSI_escape_code + +-------------------------------------------------------------------------------- + +-- Copyright (C) 2012 Ross Andrews +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Lesser General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with this program. If not, see . + +-------------------------------------------------------------------------------- + +-- A note about licensing: +-- +-- The LGPL isn't really intended to be used with non-compiled libraries. The way +-- I interpret derivative works of this library is this: if you don't modify this +-- file, and the program it's embedded in doesn't modify the Lua table it defines, +-- then you can distribute it with a program under any license. If you do either +-- of those things, then you've created a derivative work of this library and you +-- have to release the modifications you made under this same license. + +local color = { _NAME = "color" } +local _M = color + +local esc = string.char(27, 91) + +local names = {'black', 'red', 'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} +local hi_names = {'BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'PINK', 'CYAN', 'WHITE'} + +color.fg, color.bg = {}, {} + +for i, name in ipairs(names) do + color.fg[name] = esc .. tostring(30+i-1) .. 'm' + _M[name] = color.fg[name] + color.bg[name] = esc .. tostring(40+i-1) .. 'm' +end + +for i, name in ipairs(hi_names) do + color.fg[name] = esc .. tostring(90+i-1) .. 'm' + _M[name] = color.fg[name] + color.bg[name] = esc .. tostring(100+i-1) .. 'm' +end + +local function fg256(_,n) + return esc .. "38;5;" .. n .. 'm' +end + +local function bg256(_,n) + return esc .. "48;5;" .. n .. 'm' +end + +setmetatable(color.fg, {__call = fg256}) +setmetatable(color.bg, {__call = bg256}) + +color.reset = esc .. '0m' +color.clear = esc .. '2J' + +color.bold = esc .. '1m' +color.faint = esc .. '2m' +color.normal = esc .. '22m' +color.invert = esc .. '7m' +color.underline = esc .. '4m' + +color.hide = esc .. '?25l' +color.show = esc .. '?25h' + +function color.move(x, y) + return esc .. y .. ';' .. x .. 'H' +end + +color.home = color.move(1, 1) + +-------------------------------------------------- + +function color.chart(ch,col) + local cols = '0123456789abcdef' + + ch = ch or ' ' + col = col or color.fg.black + local str = color.reset .. color.bg.WHITE .. col + + for y = 0, 15 do + for x = 0, 15 do + local lbl = cols:sub(x+1, x+1) + if x == 0 then lbl = cols:sub(y+1, y+1) end + + str = str .. color.bg.black .. color.fg.WHITE .. lbl + str = str .. color.bg(x+y*16) .. col .. ch + end + str = str .. color.reset .. "\n" + end + return str .. color.reset +end + +function color.test() + print(color.reset .. color.bg.green .. color.fg.RED .. "This is bright red on green" .. color.reset) + print(color.invert .. "This is inverted..." .. color.reset .. " And this isn't.") + print(color.fg(0xDE) .. color.bg(0xEE) .. "You can use xterm-256 colors too!" .. color.reset) + print("And also " .. color.bold .. "BOLD" .. color.normal .. " if you want.") + print(color.bold .. color.fg.BLUE .. color.bg.blue .. "Miss your " .. color.fg.RED .. "C-64" .. color.fg.BLUE .. "?" .. color.reset) + print("Try printing " .. color.underline .. _M._NAME .. ".chart()" .. color.reset) +end + +return color diff --git a/onchain/permissionless-arbitration/offchain/utils/log.lua b/onchain/permissionless-arbitration/offchain/utils/log.lua new file mode 100644 index 000000000..3ed32ff5b --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/log.lua @@ -0,0 +1,35 @@ +local color = require "utils.color" + +local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} + +function log(player_index, msg) + local color_index = player_index % #names + local timestamp = os.date("%m/%d/%Y %X") + print(color.reset .. color.fg[names[color_index]] .. string.format("[#%d][%s] %s", player_index, timestamp, msg) .. color.reset) +end + +function log_to_ts(reader, last_ts) + -- print everything hold in the buffer which has smaller timestamp + -- this is to synchronise when there're gaps in between the logs + while true do + local msg = reader:read() + if msg then + print(msg) + + local ts_position = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") + if ts_position then + local timestamp = msg:sub(ts_position) + if timestamp > last_ts then + last_ts = timestamp + break + end + end + else + break + end + end + + return last_ts +end + +return { log = log, log_to_ts = log_to_ts } diff --git a/onchain/permissionless-arbitration/offchain/utils/time.lua b/onchain/permissionless-arbitration/offchain/utils/time.lua new file mode 100644 index 000000000..85e8b2931 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/time.lua @@ -0,0 +1,8 @@ +local clock = os.clock + +function sleep(number_of_seconds) + local t0 = clock() + while clock() - t0 <= number_of_seconds do end +end + +return {sleep = sleep} From 10e40ca8e5e0a443aad387da04137862c46a243d Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 4 Sep 2023 19:39:41 +0800 Subject: [PATCH 21/25] feat(arbitration): improve lua prototype * Rename `utils.log` to `utils.helper` * Fastforward blockchain when all players idle --- .../offchain/blockchain/client.lua | 21 +++++ .../offchain/entrypoint.lua | 55 +++++------ .../offchain/player/honest_strategy.lua | 40 ++++---- .../offchain/utils/helper.lua | 93 +++++++++++++++++++ .../offchain/utils/log.lua | 35 ------- 5 files changed, 166 insertions(+), 78 deletions(-) create mode 100644 onchain/permissionless-arbitration/offchain/utils/helper.lua delete mode 100644 onchain/permissionless-arbitration/offchain/utils/log.lua diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index 5b33e8c8b..d20b13cbc 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -426,6 +426,27 @@ function Client:tx_win_leaf_match( ) end +local cast_advance_template = [[ +cast rpc -r "%s" evm_increaseTime %d +]] + +function Client:advance_time(seconds) + local cmd = string.format( + cast_advance_template, + self.endpoint, + seconds + ) + + local handle = io.popen(cmd) + assert(handle) + local ret = handle:read "*a" + handle:close() + + if ret:find "Error" then + error(string.format("Advance time `%d`s failed:\n%s", seconds, ret)) + end +end + return Client diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 6b220831f..7cc0cf24b 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -4,28 +4,11 @@ package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" local machine_path = "offchain/program/simple-program" -local ps_template = [[ps %s | grep defunct | wc -l]] -local log = require 'utils.log' +local helper = require 'utils.helper' local Blockchain = require "blockchain.node" local Machine = require "computation.machine" - -local function is_zombie(pid) - local reader = io.popen(string.format(ps_template, pid)) - ret = reader:read() - reader:close() - return tonumber(ret) == 1 -end - -local function stop_players(pid_reader) - for pid, reader in pairs(pid_reader) do - print(string.format("Stopping player with pid %s...", pid)) - os.execute(string.format("kill -15 %s", pid)) - reader:close() - print "Player stopped" - end - -end +local Client = require "blockchain.client" print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" @@ -53,23 +36,45 @@ end -- gracefully end children processes setmetatable(pid_reader, { __gc = function(t) - stop_players(t) + helper.stop_players(t) end }) local no_active_players = 0 +local all_idle = 0 +local client = Client:new(1) +local last_ts = [[01/01/2000 00:00:00]] while true do - local last_ts = [[01/01/2000 00:00:00]] local players = 0 for pid, reader in pairs(pid_reader) do + local msg_out = 0 players = players + 1 - if is_zombie(pid) then - log.log(pid_player[pid], string.format("player process %s is dead", pid)) + last_ts, msg_out = helper.log_to_ts(reader, last_ts) + + -- close the reader and delete the reader entry when there's no more msg in the buffer + -- and the process has already ended + if msg_out == 0 and helper.is_zombie(pid) then + helper.log(pid_player[pid], string.format("player process %s is dead", pid)) reader:close() pid_reader[pid] = nil + pid_player[pid] = nil + end + end + + if players > 0 then + if helper.all_players_idle(pid_player) then + all_idle = all_idle + 1 + helper.rm_all_players_idle(pid_player) else - last_ts = log.log_to_ts(reader, last_ts) + all_idle = 0 + end + + -- if all players are idle for 10 consecutive iterations, advance blockchain + if all_idle == 10 then + print("all players idle, fastforward blockchain for 30 seconds...") + client:advance_time(30) + all_idle = 0 end end @@ -81,8 +86,6 @@ while true do -- if no active player processes for 10 consecutive iterations, break loop if no_active_players == 10 then break end - - -- TODO: if all players are idle, advance anvil end print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index fe676b25e..965e45498 100644 --- a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -1,6 +1,6 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers -local log = require 'utils.log' +local helper = require 'utils.helper' local Machine = require "computation.machine" local Client = require "blockchain.client" @@ -48,9 +48,9 @@ function Player:_react_tournament(tournament) if not tournament.parent then local winner_final_state = self.client:root_tournament_winner(tournament.address) if winner_final_state[1] == "true" then - log.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") - log.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) - log.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) + helper.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") + helper.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) + helper.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) return true end else @@ -58,19 +58,19 @@ function Player:_react_tournament(tournament) if tournament_winner[1] == "true" then local old_commitment = self.commitments[tournament.parent.address] if tournament_winner[2] ~= old_commitment.root_hash then - log.log(self.player_index, "player lost tournament") + helper.log(self.player_index, "player lost tournament") self.has_lost = true return end if self.called_win[tournament.address] then - log.log(self.player_index, "player already called winInnerMatch") + helper.log(self.player_index, "player already called winInnerMatch") return else self.called_win[tournament.address] = true end - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, @@ -94,7 +94,7 @@ end function Player:_react_match(match, commitment) -- TODO call timeout if needed - log.log(self.player_index, "HEIGHT: " .. match.current_height) + helper.log(self.player_index, "HEIGHT: " .. match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then @@ -106,11 +106,11 @@ function Player:_react_match(match, commitment) if finished then local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - log.log(self.player_index, "DELAY", delay - os.time()) + helper.log(self.player_index, "DELAY", delay - os.time()) return end - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "Calculating access logs for step %s", match.running_leaf )) @@ -119,7 +119,7 @@ function Player:_react_match(match, commitment) local ucycle = (match.running_leaf & constants.uarch_span):touinteger() local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -135,7 +135,7 @@ function Player:_react_match(match, commitment) logs ) if not ok then - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "win leaf match reverted: %s", e )) @@ -157,7 +157,10 @@ function Player:_react_match(match, commitment) elseif match.current_height == 1 then -- match to be sealed local found, left, right = match.current_other_parent:children() - if not found then return end + if not found then + helper.touch_player_idle(self.player_index) + return + end local initial_hash, proof if match.running_leaf:iszero() then @@ -167,7 +170,7 @@ function Player:_react_match(match, commitment) end if match.tournament.level == 1 then - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "seal leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -183,7 +186,7 @@ function Player:_react_match(match, commitment) proof ) else - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "seal inner match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, @@ -216,6 +219,7 @@ function Player:_react_match(match, commitment) -- match running local found, left, right = match.current_other_parent:children() if not found then + helper.touch_player_idle(self.player_index) return end @@ -230,7 +234,7 @@ function Player:_react_match(match, commitment) assert(f) end - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "advance match with current height %d in tournament %s of level %d for commitment %s", match.current_height, match.tournament.address, @@ -284,13 +288,15 @@ function Player:_join_tournament_if_needed(tournament, commitment) assert(f) local last, proof = commitment:last() - log.log(self.player_index, string.format( + helper.log(self.player_index, string.format( "join tournament %s of level %d with commitment %s", tournament.address, tournament.level, commitment.root_hash )) self.client:tx_join_tournament(tournament.address, last, proof, left, right) + else + helper.touch_player_idle(self.player_index) end end diff --git a/onchain/permissionless-arbitration/offchain/utils/helper.lua b/onchain/permissionless-arbitration/offchain/utils/helper.lua new file mode 100644 index 000000000..6dca7d413 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/utils/helper.lua @@ -0,0 +1,93 @@ +local color = require "utils.color" + +local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} +local idle_template = [[ls player%d_idle 2> /dev/null | grep player%d_idle | wc -l]] +local ps_template = [[ps %s | grep defunct | wc -l]] + +local function log(player_index, msg) + local color_index = (player_index - 1) % #names + 1 + local timestamp = os.date("%m/%d/%Y %X") + print(color.reset .. color.fg[names[color_index]] .. string.format("[#%d][%s] %s", player_index, timestamp, msg) .. color.reset) +end + +local function log_to_ts(reader, last_ts) + -- print everything hold in the buffer which has smaller timestamp + -- this is to synchronise when there're gaps in between the logs + local msg_output = 0 + while true do + local msg = reader:read() + if msg then + msg_output = msg_output + 1 + print(msg) + + local i, j = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") + if i and j then + local timestamp = msg:sub(i, j) + if timestamp > last_ts then + last_ts = timestamp + break + end + end + else + break + end + end + return last_ts, msg_output +end + +local function is_zombie(pid) + local reader = io.popen(string.format(ps_template, pid)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end + +local function stop_players(pid_reader) + for pid, reader in pairs(pid_reader) do + print(string.format("Stopping player with pid %s...", pid)) + os.execute(string.format("kill -15 %s", pid)) + reader:close() + print "Player stopped" + end +end + +local function touch_player_idle(player_index) + os.execute(string.format("touch player%d_idle", player_index)) +end + +local function is_player_idle(player_index) + local reader = io.popen(string.format(idle_template, player_index, player_index)) + ret = reader:read() + reader:close() + return tonumber(ret) == 1 +end + +local function rm_player_idle(player_index) + os.execute(string.format("rm player%d_idle", player_index)) +end + +local function all_players_idle(pid_player) + for pid, player in pairs(pid_player) do + if not is_player_idle(player) then + return false + end + end + return true +end + +local function rm_all_players_idle(pid_player) + for pid, player in pairs(pid_player) do + rm_player_idle(player) + end + return true +end + +return { + log = log, + log_to_ts = log_to_ts, + is_zombie = is_zombie, + stop_players = stop_players, + touch_player_idle = touch_player_idle, + all_players_idle = all_players_idle, + rm_all_players_idle = rm_all_players_idle +} diff --git a/onchain/permissionless-arbitration/offchain/utils/log.lua b/onchain/permissionless-arbitration/offchain/utils/log.lua deleted file mode 100644 index 3ed32ff5b..000000000 --- a/onchain/permissionless-arbitration/offchain/utils/log.lua +++ /dev/null @@ -1,35 +0,0 @@ -local color = require "utils.color" - -local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} - -function log(player_index, msg) - local color_index = player_index % #names - local timestamp = os.date("%m/%d/%Y %X") - print(color.reset .. color.fg[names[color_index]] .. string.format("[#%d][%s] %s", player_index, timestamp, msg) .. color.reset) -end - -function log_to_ts(reader, last_ts) - -- print everything hold in the buffer which has smaller timestamp - -- this is to synchronise when there're gaps in between the logs - while true do - local msg = reader:read() - if msg then - print(msg) - - local ts_position = msg:find("%d%d/%d%d/%d%d%d%d %d%d:%d%d:%d%d") - if ts_position then - local timestamp = msg:sub(ts_position) - if timestamp > last_ts then - last_ts = timestamp - break - end - end - else - break - end - end - - return last_ts -end - -return { log = log, log_to_ts = log_to_ts } From e7943ce4b54ef4dd718738f78b7309168dd86323 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:27:05 +0800 Subject: [PATCH 22/25] refactor(arbitration): split player state from player strategy --- .../offchain/entrypoint.lua | 7 +- .../offchain/player/dishonest_player.lua | 6 +- .../offchain/player/honest_player.lua | 8 +- .../offchain/player/honest_strategy.lua | 303 ------------------ .../offchain/player/state.lua | 119 +++++++ .../offchain/player/strategy.lua | 207 ++++++++++++ 6 files changed, 341 insertions(+), 309 deletions(-) delete mode 100644 onchain/permissionless-arbitration/offchain/player/honest_strategy.lua create mode 100644 onchain/permissionless-arbitration/offchain/player/state.lua create mode 100644 onchain/permissionless-arbitration/offchain/player/strategy.lua diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index 7cc0cf24b..b074484ab 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -71,7 +71,7 @@ while true do end -- if all players are idle for 10 consecutive iterations, advance blockchain - if all_idle == 10 then + if all_idle == 5 then print("all players idle, fastforward blockchain for 30 seconds...") client:advance_time(30) all_idle = 0 @@ -85,7 +85,10 @@ while true do end -- if no active player processes for 10 consecutive iterations, break loop - if no_active_players == 10 then break end + if no_active_players == 10 then + print("no active players, end program...") + break + end end print "Good-bye, world!" diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua index ca6bbab84..d90e8654e 100755 --- a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -3,11 +3,12 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -local Player = require "player.honest_strategy" +local Player = require "player.state" local Client = require "blockchain.client" local Hash = require "cryptography.hash" local time = require "utils.time" +local strategy = require "player.strategy" local player_index = tonumber(arg[1]) local tournament = arg[2] @@ -21,6 +22,7 @@ do end while true do - if p:react() then break end + p:fetch() + if strategy.react_honestly(p) then break end time.sleep(1) end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_player.lua b/onchain/permissionless-arbitration/offchain/player/honest_player.lua index c9c592f3c..856547297 100755 --- a/onchain/permissionless-arbitration/offchain/player/honest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -3,9 +3,12 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -local Player = require "player.honest_strategy" +local Player = require "player.state" +local Client = require "blockchain.client" +local Hash = require "cryptography.hash" local time = require "utils.time" +local strategy = require "player.strategy" local player_index = tonumber(arg[1]) local tournament = arg[2] @@ -18,6 +21,7 @@ do end while true do - if p:react() then break end + p:fetch() + if strategy.react_honestly(p) then break end time.sleep(1) end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua deleted file mode 100644 index 965e45498..000000000 --- a/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua +++ /dev/null @@ -1,303 +0,0 @@ -local constants = require "constants" -local bint = require 'utils.bint' (256) -- use 256 bits integers -local helper = require 'utils.helper' - -local Machine = require "computation.machine" -local Client = require "blockchain.client" - -local Player = {} -Player.__index = Player - -function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) - local player = { - machine_path = machine_path, - root_tournament = { - base_big_cycle = 0, - address = root_tournament_address, - level = constants.levels, - parent = false, - }, - client = Client:new(player_index), - commitment_builder = commitment_builder, - commitments = {}, - called_win = {}, - player_index = player_index - } - - setmetatable(player, self) - return player -end - -function Player:react() - if self.has_lost then - return true - end - return self:_react_tournament(self.root_tournament) -end - -function Player:_react_tournament(tournament) - local commitment = self.commitments[tournament.address] - if not commitment then - commitment = self.commitment_builder:build( - tournament.base_big_cycle, - tournament.level - ) - self.commitments[tournament.address] = commitment - end - - if not tournament.parent then - local winner_final_state = self.client:root_tournament_winner(tournament.address) - if winner_final_state[1] == "true" then - helper.log(self.player_index, "TOURNAMENT FINISHED, HURRAYYY") - helper.log(self.player_index, "Winner commitment: " .. winner_final_state[2]:hex_string()) - helper.log(self.player_index, "Final state: " .. winner_final_state[3]:hex_string()) - return true - end - else - local tournament_winner = self.client:inner_tournament_winner(tournament.address) - if tournament_winner[1] == "true" then - local old_commitment = self.commitments[tournament.parent.address] - if tournament_winner[2] ~= old_commitment.root_hash then - helper.log(self.player_index, "player lost tournament") - self.has_lost = true - return - end - - if self.called_win[tournament.address] then - helper.log(self.player_index, "player already called winInnerMatch") - return - else - self.called_win[tournament.address] = true - end - - helper.log(self.player_index, string.format( - "win tournament %s of level %d for commitment %s", - tournament.address, - tournament.level, - commitment.root_hash - )) - local _, left, right = old_commitment:children(old_commitment.root_hash) - self.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) - return - end - end - - local latest_match = self:_latest_match(tournament, commitment) - - if not latest_match then - self:_join_tournament_if_needed(tournament, commitment) - else - self:_react_match(latest_match, commitment) - end -end - -function Player:_react_match(match, commitment) - -- TODO call timeout if needed - - helper.log(self.player_index, "HEIGHT: " .. match.current_height) - if match.current_height == 0 then - -- match sealed - if match.tournament.level == 1 then - local f, left, right = commitment.root_hash:children() - assert(f) - - local finished = - self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() - - if finished then - local delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) - helper.log(self.player_index, "DELAY", delay - os.time()) - return - end - - helper.log(self.player_index, string.format( - "Calculating access logs for step %s", - match.running_leaf - )) - - local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() - local ucycle = (match.running_leaf & constants.uarch_span):touinteger() - local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - - helper.log(self.player_index, string.format( - "win leaf match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - local ok, e = pcall(self.client.tx_win_leaf_match, - self.client, - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - logs - ) - if not ok then - helper.log(self.player_index, string.format( - "win leaf match reverted: %s", - e - )) - end - else - local address = self.client:read_tournament_created( - match.tournament.address, - match.match_id_hash - ).new_tournament - - local new_tournament = {} - new_tournament.address = address - new_tournament.level = match.tournament.level - 1 - new_tournament.parent = match.tournament - new_tournament.base_big_cycle = match.base_big_cycle - - return self:_react_tournament(new_tournament) - end - elseif match.current_height == 1 then - -- match to be sealed - local found, left, right = match.current_other_parent:children() - if not found then - helper.touch_player_idle(self.player_index) - return - end - - local initial_hash, proof - if match.running_leaf:iszero() then - initial_hash, proof = commitment.implicit_hash, {} - else - initial_hash, proof = commitment:prove_leaf(match.running_leaf) - end - - if match.tournament.level == 1 then - helper.log(self.player_index, string.format( - "seal leaf match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_seal_leaf_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - initial_hash, - proof - ) - else - helper.log(self.player_index, string.format( - "seal inner match in tournament %s of level %d for commitment %s", - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_seal_inner_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - initial_hash, - proof - ) - - local address = self.client:read_tournament_created( - match.tournament.address, - match.match_id_hash - ).new_tournament - - local new_tournament = {} - new_tournament.address = address - new_tournament.level = match.tournament.level - 1 - new_tournament.parent = match.tournament - new_tournament.base_big_cycle = match.base_big_cycle - - return self:_react_tournament(new_tournament) - end - else - -- match running - local found, left, right = match.current_other_parent:children() - if not found then - helper.touch_player_idle(self.player_index) - return - end - - local new_left, new_right - if left ~= match.current_left then - local f - f, new_left, new_right = left:children() - assert(f) - else - local f - f, new_left, new_right = right:children() - assert(f) - end - - helper.log(self.player_index, string.format( - "advance match with current height %d in tournament %s of level %d for commitment %s", - match.current_height, - match.tournament.address, - match.tournament.level, - commitment.root_hash - )) - self.client:tx_advance_match( - match.tournament.address, - match.commitment_one, - match.commitment_two, - left, - right, - new_left, - new_right - ) - end -end - -function Player:_latest_match(tournament, commitment) - local matches = self.client:read_match_created(tournament.address, commitment.root_hash) - local last_match = matches[#matches] - - if not last_match then return false end - - local m = self.client:match(tournament.address, last_match.match_id_hash) - if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then - return false - end - last_match.current_other_parent = m[1] - last_match.current_left = m[2] - last_match.current_right = m[3] - last_match.running_leaf = bint(m[4]) - last_match.current_height = tonumber(m[5]) - last_match.level = tonumber(m[6]) - last_match.tournament = tournament - - local level = tournament.level - local base = bint(tournament.base_big_cycle) - local step = bint(1) << constants.log2step[level] - last_match.leaf_cycle = base + (step * last_match.running_leaf) - last_match.base_big_cycle = (last_match.leaf_cycle >> constants.log2_uarch_span):touinteger() - - return last_match -end - -function Player:_join_tournament_if_needed(tournament, commitment) - local c = self.client:read_commitment(tournament.address, commitment.root_hash) - - if c.clock.allowance == 0 then - local f, left, right = commitment:children(commitment.root_hash) - assert(f) - local last, proof = commitment:last() - - helper.log(self.player_index, string.format( - "join tournament %s of level %d with commitment %s", - tournament.address, - tournament.level, - commitment.root_hash - )) - self.client:tx_join_tournament(tournament.address, last, proof, left, right) - else - helper.touch_player_idle(self.player_index) - end -end - -return Player diff --git a/onchain/permissionless-arbitration/offchain/player/state.lua b/onchain/permissionless-arbitration/offchain/player/state.lua new file mode 100644 index 000000000..de1a2d5fa --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/state.lua @@ -0,0 +1,119 @@ +local constants = require "constants" +local bint = require 'utils.bint' (256) -- use 256 bits integers + +local Client = require "blockchain.client" + +local Player = {} +Player.__index = Player + +function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) + local player = { + machine_path = machine_path, + root_tournament = { + base_big_cycle = 0, + address = root_tournament_address, + level = constants.levels, + parent = false, + commitment = false, + commitment_status = {}, + tournament_winner = {}, + latest_match = {}, + }, + client = Client:new(player_index), + commitment_builder = commitment_builder, + player_index = player_index + } + + setmetatable(player, self) + return player +end + +function Player:fetch() + return self:_fetch_tournament(self.root_tournament) +end + +function Player:_fetch_tournament(tournament) + local commitment = tournament.commitment + if not commitment then + commitment = self.commitment_builder:build( + tournament.base_big_cycle, + tournament.level + ) + tournament.commitment = commitment + end + + if not tournament.parent then + tournament.tournament_winner = self.client:root_tournament_winner(tournament.address) + else + tournament.tournament_winner = self.client:inner_tournament_winner(tournament.address) + end + + tournament.latest_match = self:_latest_match(tournament) + + if not tournament.latest_match then + tournament.commitment_status = self.client:read_commitment(tournament.address, commitment.root_hash) + else + self:_fetch_match(tournament.latest_match, commitment) + end +end + +function Player:_fetch_match(match, commitment) + if match.current_height == 0 then + -- match sealed + if match.tournament.level == 1 then + local f, left, right = commitment.root_hash:children() + assert(f) + + match.finished = + self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() + + if match.finished then + match.delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) + end + else + local address = self.client:read_tournament_created( + match.tournament.address, + match.match_id_hash + ).new_tournament + + local new_tournament = {} + new_tournament.address = address + new_tournament.level = match.tournament.level - 1 + new_tournament.parent = match.tournament + new_tournament.base_big_cycle = match.base_big_cycle + match.inner_tournament = new_tournament + + return self:_fetch_tournament(new_tournament) + end + end +end + +function Player:_latest_match(tournament) + local commitment = tournament.commitment + local matches = self.client:read_match_created(tournament.address, commitment.root_hash) + local last_match = matches[#matches] + + if not last_match then return false end + + local m = self.client:match(tournament.address, last_match.match_id_hash) + if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then + return false + end + last_match.current_other_parent = m[1] + last_match.current_left = m[2] + last_match.current_right = m[3] + last_match.running_leaf = bint(m[4]) + last_match.current_height = tonumber(m[5]) + last_match.level = tonumber(m[6]) + last_match.tournament = tournament + + local level = tournament.level + local base = bint(tournament.base_big_cycle) + local step = bint(1) << constants.log2step[level] + last_match.leaf_cycle = base + (step * last_match.running_leaf) + last_match.base_big_cycle = (last_match.leaf_cycle >> constants.log2_uarch_span):touinteger() + + return last_match +end + +return Player diff --git a/onchain/permissionless-arbitration/offchain/player/strategy.lua b/onchain/permissionless-arbitration/offchain/player/strategy.lua new file mode 100644 index 000000000..b35ac0100 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/player/strategy.lua @@ -0,0 +1,207 @@ +local constants = require "constants" +local helper = require 'utils.helper' + +local Machine = require "computation.machine" + +local _react_match_honestly +local _react_tournament_honestly + +local function _join_tournament_if_needed(player, tournament) + if tournament.commitment_status.clock.allowance == 0 then + local f, left, right = tournament.commitment:children(tournament.commitment.root_hash) + assert(f) + local last, proof = tournament.commitment:last() + + helper.log(player.player_index, string.format( + "join tournament %s of level %d with commitment %s", + tournament.address, + tournament.level, + tournament.commitment.root_hash + )) + player.client:tx_join_tournament(tournament.address, last, proof, left, right) + else + helper.touch_player_idle(player.player_index) + end +end + +_react_match_honestly = function(player, match, commitment) + -- TODO call timeout if needed + + helper.log(player.player_index, "HEIGHT: " .. match.current_height) + if match.current_height == 0 then + -- match sealed + if match.tournament.level == 1 then + local f, left, right = commitment.root_hash:children() + assert(f) + + helper.log(player.player_index, string.format( + "Calculating access logs for step %s", + match.running_leaf + )) + + local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() + local ucycle = (match.running_leaf & constants.uarch_span):touinteger() + local logs = Machine:get_logs(player.machine_path, cycle, ucycle) + + helper.log(player.player_index, string.format( + "win leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + local ok, e = pcall(player.client.tx_win_leaf_match, + player.client, + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + logs + ) + if not ok then + helper.log(player.player_index, string.format( + "win leaf match reverted: %s", + e + )) + end + else + return _react_tournament_honestly(player, match.inner_tournament) + end + elseif match.current_height == 1 then + -- match to be sealed + local found, left, right = match.current_other_parent:children() + if not found then + helper.touch_player_idle(player.player_index) + return + end + + local initial_hash, proof + if match.running_leaf:iszero() then + initial_hash, proof = commitment.implicit_hash, {} + else + initial_hash, proof = commitment:prove_leaf(match.running_leaf) + end + + if match.tournament.level == 1 then + helper.log(player.player_index, string.format( + "seal leaf match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + player.client:tx_seal_leaf_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + else + helper.log(player.player_index, string.format( + "seal inner match in tournament %s of level %d for commitment %s", + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + player.client:tx_seal_inner_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + initial_hash, + proof + ) + end + else + -- match running + local found, left, right = match.current_other_parent:children() + if not found then + helper.touch_player_idle(player.player_index) + return + end + + local new_left, new_right + if left ~= match.current_left then + local f + f, new_left, new_right = left:children() + assert(f) + else + local f + f, new_left, new_right = right:children() + assert(f) + end + + helper.log(player.player_index, string.format( + "advance match with current height %d in tournament %s of level %d for commitment %s", + match.current_height, + match.tournament.address, + match.tournament.level, + commitment.root_hash + )) + player.client:tx_advance_match( + match.tournament.address, + match.commitment_one, + match.commitment_two, + left, + right, + new_left, + new_right + ) + end +end + +_react_tournament_honestly = function(player, tournament) + local tournament_winner = tournament.tournament_winner + if tournament_winner[1] == "true" then + if not tournament.parent then + helper.log(player.player_index, "TOURNAMENT FINISHED, HURRAYYY") + helper.log(player.player_index, "Winner commitment: " .. tournament_winner[2]:hex_string()) + helper.log(player.player_index, "Final state: " .. tournament_winner[3]:hex_string()) + return true + else + local old_commitment = tournament.parent.commitment + if tournament_winner[2] ~= old_commitment.root_hash then + helper.log(player.player_index, "player lost tournament") + player.has_lost = true + return + end + + if tournament.called_win then + helper.log(player.player_index, "player already called winInnerMatch") + return + else + tournament.called_win = true + end + + helper.log(player.player_index, string.format( + "win tournament %s of level %d for commitment %s", + tournament.address, + tournament.level, + tournament.commitment.root_hash + )) + local _, left, right = old_commitment:children(old_commitment.root_hash) + player.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) + return + end + end + + if not tournament.latest_match then + _join_tournament_if_needed(player, tournament) + else + _react_match_honestly(player, tournament.latest_match, tournament.commitment) + end +end + +local function _react_honestly(player) + if player.has_lost then + return true + end + return _react_tournament_honestly(player, player.root_tournament) +end + +return { + react_honestly = _react_honestly +} From dde4585dfa6133b5480de148ee19001756fa61db Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:27:35 +0800 Subject: [PATCH 23/25] chore(arbitration): increase anvil accounts to 40 --- .../offchain/blockchain/client.lua | 96 ------------------- .../offchain/blockchain/constants.lua | 62 +++++++++++- .../offchain/blockchain/node.lua | 25 ++--- 3 files changed, 70 insertions(+), 113 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/client.lua index d20b13cbc..26ded24dd 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/client.lua @@ -448,99 +448,3 @@ function Client:advance_time(seconds) end return Client - - ---[[ -local blockchain = require "blockchain":new() -local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -local contract = blockchain:deploy_contract(initial_hash) - -local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) -local p1_l1 = utils.keccak "0" -local p1_l2 = utils.keccak "1" -local p1_cmt = utils.join_hashes(utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) -client1:tx_join_tournament(contract, p1_l2, {p1_l1}, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2)) -local commitment1 = client1:read_commitment(contract, p1_cmt) -print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) - -local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) -local p2_l1 = utils.keccak "0" -local p2_l2 = utils.keccak "2" -local p2_cmt = utils.join_hashes(utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) -client2:tx_join_tournament(contract, p2_l2, {p2_l1}, utils.join_hashes(p2_l1, p2_l2), utils.join_hashes(p2_l2, p2_l2)) -local commitment2 = client1:read_commitment(contract, p2_cmt) -print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) - -local a = client1:read_match_created(contract, false, false) -for k,log in ipairs(a) do - print("LOG ", k) - print("tournament_address", log.tournament_address) - print("commitment_one", log.commitment_one) - print("commitment_two", log.commitment_two) - print("match_id_hash", log.match_id_hash) - print("left_hash", log.left_hash) - print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) - print("END ", k) - print("") - - local x = client1:match(contract, log.match_id_hash) - for _,v in ipairs(x) do print("A", v) end -end - -client1:tx_advance_match(contract, p1_cmt, p2_cmt, utils.join_hashes(p1_l1, p1_l2), utils.join_hashes(p1_l2, p1_l2), p1_l1, p1_l2) - -client2:tx_seal_inner_match(contract, p1_cmt, p2_cmt, p2_l1, p2_l2, p2_l1, {}) -client1:read_tournament_created(contract, utils.join_hashes(p1_cmt, p2_cmt)) ---]] - --- local client1 = Client:new(blockchain.endpoint, blockchain:new_account()) --- local p1_l1 = utils.keccak "0" --- local p1_l2 = utils.keccak "1" --- local p1_cmt = utils.join_hashes(p1_l1, p1_l2) --- client1:tx_join_tournament(contract, p1_l2, {p1_l1}, p1_l1, p1_l2) --- local commitment1 = client1:read_commitment(contract, p1_cmt) --- print(commitment1.clock.allowance, commitment1.clock.last_resume, commitment1.final_state) - --- local client2 = Client:new(blockchain.endpoint, blockchain:new_account()) --- local p2_l1 = utils.keccak "0" --- local p2_l2 = utils.keccak "2" --- local p2_cmt = utils.join_hashes(p2_l1, p2_l2) --- client2:tx_join_tournament(contract, p2_l2, {p2_l1}, p2_l1, p2_l2) --- local commitment2 = client1:read_commitment(contract, p2_cmt) --- print(commitment2.clock.allowance, commitment2.clock.last_resume, commitment2.final_state) - --- local a = client1:read_match_created(contract, false, false) --- for k,log in ipairs(a) do --- print("LOG ", k) --- print("tournament_address", log.tournament_address) --- print("commitment_one", log.commitment_one) --- print("commitment_two", log.commitment_two) --- print("match_id_hash", log.match_id_hash) --- print("left_hash", log.left_hash) --- print("meta", log.meta.block_hash, log.meta.block_number, log.meta.log_index) --- print("END ", k) --- print("") - --- local x = client1:match(contract, log.match_id_hash) --- for _,v in ipairs(x) do print("A", v) end --- end - - - --- client.joinRootTournament(commitmentId, leftChild, rightChild) --- client.winByTimeout(id) --- client.advanceMatch(id, leftChild, rightChild, leftChildChild, rightChildChild) --- client.sealMatch() // seals match, creates nested tournament, updates current commitment --- client.enterNestedTournament(parentMatch, parentCommitmentIdHash, childCommitment) --- client.proveLeafMatch() - - --- local LEVEL_LOG2_STEP_SIZES = { 24, 14, 7, 0 } --- local LOG2_MAX_MCYCLE = 63 --- local last_log2_num_steps = LOG2_MAX_MCYCLE - --- for i, log2_step_size in ipairs(LEVEL_LOG2_STEP_SIZES) do --- local log2_num_steps = last_log2_num_steps - log2_step_size --- print(i, log2_num_steps) --- last_log2_num_steps = log2_step_size --- end diff --git a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua index 75a963629..3949135c7 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/constants.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/constants.lua @@ -1,4 +1,4 @@ --- contains default 10 accounts of anvil test node, and deployed tournament contract address +-- contains default 40 accounts of anvil test node local constants = { endpoint = "http://127.0.0.1:8545", addresses = { @@ -12,6 +12,36 @@ local constants = { "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + "0xBcd4042DE499D14e55001CcbB24a551F3b954096", + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788", + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a", + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec", + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097", + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71", + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30", + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E", + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0", + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199", + "0x09DB0a93B389bEF724429898f539AEB7ac2Dd55f", + "0x02484cb50AAC86Eae85610D6f4Bf026f30f6627D", + "0x08135Da0A343E492FA2d4282F2AE34c6c5CC1BbE", + "0x5E661B79FE2D3F6cE70F5AAC07d8Cd9abb2743F1", + "0x61097BA76cD906d2ba4FD106E757f7Eb455fc295", + "0xDf37F81dAAD2b0327A0A50003740e1C935C70913", + "0x553BC17A05702530097c3677091C5BB47a3a7931", + "0x87BdCE72c06C21cd96219BD8521bDF1F42C78b5e", + "0x40Fc963A729c542424cD800349a7E4Ecc4896624", + "0x9DCCe783B6464611f38631e6C851bf441907c710", + "0x1BcB8e569EedAb4668e55145Cfeaf190902d3CF2", + "0x8263Fce86B1b78F95Ab4dae11907d8AF88f841e7", + "0xcF2d5b3cBb4D7bF04e3F7bFa8e27081B52191f91", + "0x86c53Eb85D0B7548fea5C4B4F82b4205C8f6Ac18", + "0x1aac82773CB722166D7dA0d5b0FA35B0307dD99D", + "0x2f4f06d218E426344CFE1A83D53dAd806994D325", + "0x1003ff39d25F2Ab16dBCc18EcE05a9B6154f65F4", + "0x9eAF5590f2c84912A08de97FA28d0529361Deb9E", + "0x11e8F3eA3C6FcF12EcfF2722d75CEFC539c51a1C", + "0x7D86687F980A56b832e9378952B738b614A99dc6", }, pks = { "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", @@ -24,6 +54,36 @@ local constants = { "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897", + "0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82", + "0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1", + "0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd", + "0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa", + "0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61", + "0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0", + "0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd", + "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", + "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e", + "0xeaa861a9a01391ed3d587d8a5a84ca56ee277629a8b02c22093a419bf240e65d", + "0xc511b2aa70776d4ff1d376e8537903dae36896132c90b91d52c1dfbae267cd8b", + "0x224b7eb7449992aac96d631d9677f7bf5888245eef6d6eeda31e62d2f29a83e4", + "0x4624e0802698b9769f5bdb260a3777fbd4941ad2901f5966b854f953497eec1b", + "0x375ad145df13ed97f8ca8e27bb21ebf2a3819e9e0a06509a812db377e533def7", + "0x18743e59419b01d1d846d97ea070b5a3368a3e7f6f0242cf497e1baac6972427", + "0xe383b226df7c8282489889170b0f68f66af6459261f4833a781acd0804fafe7a", + "0xf3a6b71b94f5cd909fb2dbb287da47badaa6d8bcdc45d595e2884835d8749001", + "0x4e249d317253b9641e477aba8dd5d8f1f7cf5250a5acadd1229693e262720a19", + "0x233c86e887ac435d7f7dc64979d7758d69320906a0d340d2b6518b0fd20aa998", + "0x85a74ca11529e215137ccffd9c95b2c72c5fb0295c973eb21032e823329b3d2d", + "0xac8698a440d33b866b6ffe8775621ce1a4e6ebd04ab7980deb97b3d997fc64fb", + "0xf076539fbce50f0513c488f32bf81524d30ca7a29f400d68378cc5b1b17bc8f2", + "0x5544b8b2010dbdbef382d254802d856629156aba578f453a76af01b81a80104e", + "0x47003709a0a9a4431899d4e014c1fd01c5aad19e873172538a02370a119bae11", + "0x9644b39377553a920edc79a275f45fa5399cbcf030972f771d0bca8097f9aad3", + "0xcaa7b4a2d30d1d565716199f068f69ba5df586cf32ce396744858924fdf827f0", + "0xfc5a028670e1b6381ea876dd444d3faaee96cffae6db8d93ca6141130259247c", + "0x5b92c5fe82d4fabee0bc6d95b4b8a3f9680a0ed7801f631035528f32c9eb2ad5", + "0xb68ac4aa2137dd31fd0732436d8e59e959bb62b4db2e6107b15f594caf0f405f", }, } diff --git a/onchain/permissionless-arbitration/offchain/blockchain/node.lua b/onchain/permissionless-arbitration/offchain/blockchain/node.lua index 93aaf98cb..7dee3044e 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/node.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/node.lua @@ -1,4 +1,4 @@ -local default_account_number = 10 +local default_account_number = 40 local function stop_blockchain(handle, pid) print(string.format("Stopping blockchain with pid %d...", pid)) @@ -7,11 +7,10 @@ local function stop_blockchain(handle, pid) print "Blockchain stopped" end -local function start_blockchain(account_num) - account_num = account_num or default_account_number - print(string.format("Starting blockchain with %d accounts...", account_num)) +local function start_blockchain() + print(string.format("Starting blockchain with %d accounts...", default_account_number)) - local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d > anvil.log 2>&1"]], account_num) + local cmd = string.format([[sh -c "echo $$ ; exec anvil --block-time 1 -a %d > anvil.log 2>&1"]], default_account_number) local reader = io.popen(cmd) assert(reader, "`popen` returned nil reader") @@ -147,24 +146,22 @@ end local Blockchain = {} Blockchain.__index = Blockchain -function Blockchain:new(account_num) +function Blockchain:new() local blockchain = {} - local handle = start_blockchain(account_num) + local handle = start_blockchain() local accounts, endpoint = capture_blockchain_data() blockchain._handle = handle blockchain._accounts = accounts - blockchain._current_account = 1 blockchain.endpoint = endpoint setmetatable(blockchain, self) return blockchain end -function Blockchain:new_account() - local current_account = self._current_account - self._current_account = current_account + 1 +function Blockchain:default_account() + local current_account = 1 local accounts = self._accounts assert(current_account <= #accounts.address, "no more accounts") @@ -178,13 +175,9 @@ end function Blockchain:deploy_contract(initial_hash, deployer) assert(initial_hash) - deployer = deployer or self:new_account() + deployer = deployer or self:default_account() local address = deploy_contracts(self.endpoint, deployer.pk, initial_hash) return address, deployer end --- local bc = Blockchain:new(100) --- local initial_hash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" --- bc:deploy_contract(initial_hash) - return Blockchain From bf7ebc81e05a0924edbbeeb006e30a31d2ef71c1 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:03:28 +0800 Subject: [PATCH 24/25] fix(arbitration): use `pcall` on every tx in case of race condition --- .../offchain/player/strategy.lua | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/onchain/permissionless-arbitration/offchain/player/strategy.lua b/onchain/permissionless-arbitration/offchain/player/strategy.lua index b35ac0100..c48cf0cf3 100644 --- a/onchain/permissionless-arbitration/offchain/player/strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/strategy.lua @@ -18,7 +18,20 @@ local function _join_tournament_if_needed(player, tournament) tournament.level, tournament.commitment.root_hash )) - player.client:tx_join_tournament(tournament.address, last, proof, left, right) + local ok, e = pcall(player.client.tx_join_tournament, + player.client, + tournament.address, + last, + proof, + left, + right + ) + if not ok then + helper.log(player.player_index, string.format( + "join tournament reverted: %s", + e + )) + end else helper.touch_player_idle(player.player_index) end @@ -89,7 +102,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_seal_leaf_match( + local ok, e = pcall(player.client.tx_seal_leaf_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -98,6 +112,12 @@ _react_match_honestly = function(player, match, commitment) initial_hash, proof ) + if not ok then + helper.log(player.player_index, string.format( + "seal leaf match reverted: %s", + e + )) + end else helper.log(player.player_index, string.format( "seal inner match in tournament %s of level %d for commitment %s", @@ -105,7 +125,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_seal_inner_match( + local ok, e = pcall(player.client.tx_seal_inner_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -114,6 +135,12 @@ _react_match_honestly = function(player, match, commitment) initial_hash, proof ) + if not ok then + helper.log(player.player_index, string.format( + "seal inner match reverted: %s", + e + )) + end end else -- match running @@ -141,7 +168,8 @@ _react_match_honestly = function(player, match, commitment) match.tournament.level, commitment.root_hash )) - player.client:tx_advance_match( + local ok, e = pcall(player.client.tx_advance_match, + player.client, match.tournament.address, match.commitment_one, match.commitment_two, @@ -150,6 +178,12 @@ _react_match_honestly = function(player, match, commitment) new_left, new_right ) + if not ok then + helper.log(player.player_index, string.format( + "advance match reverted: %s", + e + )) + end end end @@ -183,7 +217,19 @@ _react_tournament_honestly = function(player, tournament) tournament.commitment.root_hash )) local _, left, right = old_commitment:children(old_commitment.root_hash) - player.client:tx_win_inner_match(tournament.parent.address, tournament.address, left, right) + local ok, e = pcall(player.client.tx_win_inner_match, + player.client, + tournament.parent.address, + tournament.address, + left, + right + ) + if not ok then + helper.log(player.player_index, string.format( + "win inner match reverted: %s", + e + )) + end return end end From 44a235b6648eb6373e7d871e419fcb250ab59476 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:20:07 +0800 Subject: [PATCH 25/25] feat(arbitration): resolve PR comments * Split `Client` into `Reader` and `Sender` * Add variables to control behaviors in entrypoint * Add `HonestStrategy` concrete type * Move `pcall` into `Sender:_send_tx` * `State` tracks all tournaments/commitments/matches * Optimize `tournament` and `match` structure * Optimize `player.idle` logic * Add `commitmentJoined` event --- .../blockchain/{client.lua => reader.lua} | 184 ++++-------------- .../offchain/blockchain/sender.lua | 161 +++++++++++++++ .../offchain/blockchain/utils.lua | 22 +++ .../offchain/entrypoint.lua | 21 +- .../offchain/player/dishonest_player.lua | 26 ++- .../offchain/player/honest_player.lua | 26 ++- .../{strategy.lua => honest_strategy.lua} | 167 ++++++++-------- .../offchain/player/state.lua | 123 ++++++------ .../offchain/utils/helper.lua | 7 +- .../src/tournament/abstracts/Tournament.sol | 2 + 10 files changed, 423 insertions(+), 316 deletions(-) rename onchain/permissionless-arbitration/offchain/blockchain/{client.lua => reader.lua} (59%) create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/sender.lua create mode 100644 onchain/permissionless-arbitration/offchain/blockchain/utils.lua rename onchain/permissionless-arbitration/offchain/player/{strategy.lua => honest_strategy.lua} (56%) diff --git a/onchain/permissionless-arbitration/offchain/blockchain/client.lua b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua similarity index 59% rename from onchain/permissionless-arbitration/offchain/blockchain/client.lua rename to onchain/permissionless-arbitration/offchain/blockchain/reader.lua index 26ded24dd..3a257683a 100644 --- a/onchain/permissionless-arbitration/offchain/blockchain/client.lua +++ b/onchain/permissionless-arbitration/offchain/blockchain/reader.lua @@ -1,5 +1,4 @@ local Hash = require "cryptography.hash" -local MerkleTree = require "cryptography.merkle_tree" local eth_ebi = require "utils.eth_ebi" local function parse_topics(json) @@ -107,51 +106,18 @@ local function sort_and_dedup(t) return ret end -local function quote_args(args, not_quote) - local quoted_args = {} - for _, v in ipairs(args) do - if type(v) == "table" and (getmetatable(v) == Hash or getmetatable(v) == MerkleTree) then - if not_quote then - table.insert(quoted_args, v:hex_string()) - else - table.insert(quoted_args, '"' .. v:hex_string() .. '"') - end - elseif type(v) == "table" then - if v._tag == "tuple" then - local qa = quote_args(v, true) - local ca = table.concat(qa, ",") - local sb = "'(" .. ca .. ")'" - table.insert(quoted_args, sb) - else - local qa = quote_args(v, true) - local ca = table.concat(qa, ",") - local sb = "'[" .. ca .. "]'" - table.insert(quoted_args, sb) - end - elseif not_quote then - table.insert(quoted_args, tostring(v)) - else - table.insert(quoted_args, '"' .. v .. '"') - end - end +local Reader = {} +Reader.__index = Reader - return quoted_args -end - - -local Client = {} -Client.__index = Client - -function Client:new(account_index) +function Reader:new() local blockchain_data = require "blockchain.constants" - local client = { - endpoint = blockchain_data.endpoint, - pk = blockchain_data.pks[account_index], + local reader = { + endpoint = blockchain_data.endpoint } - setmetatable(client, self) - return client + setmetatable(reader, self) + return reader end local cast_logs_template = [==[ @@ -159,7 +125,7 @@ cast rpc -r "%s" eth_getLogs \ '[{"fromBlock": "earliest", "toBlock": "latest", "address": "%s", "topics": [%s]}]' -w 2>&1 ]==] -function Client:_read_logs(tournament_address, sig, topics, data_sig) +function Reader:_read_logs(tournament_address, sig, topics, data_sig) topics = topics or { false, false, false } local encoded_sig = eth_ebi.encode_sig(sig) table.insert(topics, 1, encoded_sig) @@ -201,7 +167,7 @@ local cast_call_template = [==[ cast call --rpc-url "%s" "%s" "%s" %s 2>&1 ]==] -function Client:_call(address, sig, args) +function Reader:_call(address, sig, args) local quoted_args = {} for _, v in ipairs(args) do table.insert(quoted_args, '"' .. v .. '"') @@ -236,14 +202,11 @@ function Client:_call(address, sig, args) return ret end -function Client:read_match_created(tournament_address, commitment_hash) +function Reader:read_match_created(tournament_address, commitment_hash) local sig = "matchCreated(bytes32,bytes32,bytes32)" local data_sig = "(bytes32)" - local logs1 = self:_read_logs(tournament_address, sig, { commitment_hash:hex_string(), false, false }, data_sig) - local logs2 = self:_read_logs(tournament_address, sig, { false, commitment_hash:hex_string(), false }, data_sig) - - local logs = sort_and_dedup(join_tables(logs1, logs2)) + local logs = self:_read_logs(tournament_address, sig, { false, false, false }, data_sig) local ret = {} for k, v in ipairs(logs) do @@ -262,7 +225,26 @@ function Client:read_match_created(tournament_address, commitment_hash) return ret end -function Client:read_commitment(tournament_address, commitment_hash) +function Reader:read_commitment_joined(tournament_address) + local sig = "commitmentJoined(bytes32)" + local data_sig = "(bytes32)" + + local logs = self:_read_logs(tournament_address, sig, { false, false, false }, data_sig) + + local ret = {} + for k, v in ipairs(logs) do + local log = {} + log.tournament_address = tournament_address + log.meta = v.meta + log.root = Hash:from_digest_hex(v.decoded_data[1]) + + ret[k] = log + end + + return ret +end + +function Reader:read_commitment(tournament_address, commitment_hash) local sig = "getCommitment(bytes32)((uint64,uint64),bytes32)" local call_ret = self:_call(tournament_address, sig, { commitment_hash:hex_string() }) @@ -284,7 +266,7 @@ function Client:read_commitment(tournament_address, commitment_hash) return ret end -function Client:read_tournament_created(tournament_address, match_id_hash) +function Reader:read_tournament_created(tournament_address, match_id_hash) local sig = "newInnerTournament(bytes32,address)" local data_sig = "(address)" @@ -302,7 +284,7 @@ function Client:read_tournament_created(tournament_address, match_id_hash) return ret end -function Client:match(address, match_id_hash) +function Reader:match(address, match_id_hash) local sig = "getMatch(bytes32)(bytes32,bytes32,bytes32,uint256,uint64,uint64)" local ret = self:_call(address, sig, { match_id_hash:hex_string() }) ret[1] = Hash:from_digest_hex(ret[1]) @@ -312,7 +294,7 @@ function Client:match(address, match_id_hash) return ret end -function Client:inner_tournament_winner(address) +function Reader:inner_tournament_winner(address) local sig = "innerTournamentWinner()(bool,bytes32)" local ret = self:_call(address, sig, {}) ret[2] = Hash:from_digest_hex(ret[2]) @@ -320,7 +302,7 @@ function Client:inner_tournament_winner(address) return ret end -function Client:root_tournament_winner(address) +function Reader:root_tournament_winner(address) local sig = "arbitrationResult()(bool,bytes32,bytes32)" local ret = self:_call(address, sig, {}) ret[2] = Hash:from_digest_hex(ret[2]) @@ -329,108 +311,18 @@ function Client:root_tournament_winner(address) return ret end -function Client:maximum_delay(address) +function Reader:maximum_delay(address) local sig = "maximumEnforceableDelay()(uint64)" local ret = self:_call(address, sig, {}) return ret end -local cast_send_template = [[ -cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 -]] - -function Client:_send_tx(tournament_address, sig, args) - local quoted_args = quote_args(args) - local args_str = table.concat(quoted_args, " ") - - local cmd = string.format( - cast_send_template, - self.pk, - self.endpoint, - tournament_address, - sig, - args_str - ) - - local handle = io.popen(cmd) - assert(handle) - - local ret = handle:read "*a" - if ret:find "Error" then - handle:close() - error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) - end - handle:close() -end - -function Client:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) - local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] - self:_send_tx(tournament_address, sig, { final_state, proof, left_child, right_child }) -end - -function Client:tx_advance_match( - tournament_address, commitment_one, commitment_two, left, right, new_left, new_right -) - local sig = [[advanceMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32)]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, new_left, new_right } - ) -end - -function Client:tx_seal_inner_match( - tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof -) - local sig = - [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash:hex_string(), proof } - ) -end - -function Client:tx_win_inner_match(tournament_address, child_tournament_address, left, right) - local sig = - [[winInnerMatch(address,bytes32,bytes32)]] - self:_send_tx( - tournament_address, - sig, - { child_tournament_address, left, right } - ) -end - -function Client:tx_seal_leaf_match( - tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof -) - local sig = - [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } - ) -end - -function Client:tx_win_leaf_match( - tournament_address, commitment_one, commitment_two, left, right, proof -) - local sig = - [[winLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes)]] - self:_send_tx( - tournament_address, - sig, - { { commitment_one, commitment_two, _tag = "tuple" }, left, right, proof } - ) -end - local cast_advance_template = [[ cast rpc -r "%s" evm_increaseTime %d ]] -function Client:advance_time(seconds) +function Reader:advance_time(seconds) local cmd = string.format( cast_advance_template, self.endpoint, @@ -447,4 +339,4 @@ function Client:advance_time(seconds) end end -return Client +return Reader diff --git a/onchain/permissionless-arbitration/offchain/blockchain/sender.lua b/onchain/permissionless-arbitration/offchain/blockchain/sender.lua new file mode 100644 index 000000000..305459ff1 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/sender.lua @@ -0,0 +1,161 @@ +local Hash = require "cryptography.hash" +local MerkleTree = require "cryptography.merkle_tree" + +local function quote_args(args, not_quote) + local quoted_args = {} + for _, v in ipairs(args) do + if type(v) == "table" and (getmetatable(v) == Hash or getmetatable(v) == MerkleTree) then + if not_quote then + table.insert(quoted_args, v:hex_string()) + else + table.insert(quoted_args, '"' .. v:hex_string() .. '"') + end + elseif type(v) == "table" then + if v._tag == "tuple" then + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'(" .. ca .. ")'" + table.insert(quoted_args, sb) + else + local qa = quote_args(v, true) + local ca = table.concat(qa, ",") + local sb = "'[" .. ca .. "]'" + table.insert(quoted_args, sb) + end + elseif not_quote then + table.insert(quoted_args, tostring(v)) + else + table.insert(quoted_args, '"' .. v .. '"') + end + end + + return quoted_args +end + + +local Sender = {} +Sender.__index = Sender + +function Sender:new(account_index) + local blockchain_data = require "blockchain.constants" + + local sender = { + endpoint = blockchain_data.endpoint, + pk = blockchain_data.pks[account_index], + index = account_index, + tx_count = 0 + } + + setmetatable(sender, self) + return sender +end + +local cast_send_template = [[ +cast send --private-key "%s" --rpc-url "%s" "%s" "%s" %s 2>&1 +]] + +function Sender:_send_tx(tournament_address, sig, args) + local quoted_args = quote_args(args) + local args_str = table.concat(quoted_args, " ") + + local cmd = string.format( + cast_send_template, + self.pk, + self.endpoint, + tournament_address, + sig, + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local ret = handle:read "*a" + if ret:find "Error" then + handle:close() + error(string.format("Send transaction `%s` reverted:\n%s", sig, ret)) + end + + self.tx_count = self.tx_count + 1 + handle:close() +end + +function Sender:tx_join_tournament(tournament_address, final_state, proof, left_child, right_child) + local sig = [[joinTournament(bytes32,bytes32[],bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { final_state, proof, left_child, right_child } + ) +end + +function Sender:tx_advance_match( + tournament_address, commitment_one, commitment_two, left, right, new_left, new_right +) + local sig = [[advanceMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, new_left, new_right } + ) +end + +function Sender:tx_seal_inner_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealInnerMatchAndCreateInnerTournament((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash:hex_string(), proof } + ) +end + +function Sender:tx_win_inner_match(tournament_address, child_tournament_address, left, right) + local sig = + [[winInnerMatch(address,bytes32,bytes32)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { child_tournament_address, left, right } + ) +end + +function Sender:tx_seal_leaf_match( + tournament_address, commitment_one, commitment_two, left, right, initial_hash, proof +) + local sig = + [[sealLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes32,bytes32[])]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, initial_hash, proof } + ) +end + +function Sender:tx_win_leaf_match( + tournament_address, commitment_one, commitment_two, left, right, proof +) + local sig = + [[winLeafMatch((bytes32,bytes32),bytes32,bytes32,bytes)]] + return pcall( + self._send_tx, + self, + tournament_address, + sig, + { { commitment_one, commitment_two, _tag = "tuple" }, left, right, proof } + ) +end + +return Sender diff --git a/onchain/permissionless-arbitration/offchain/blockchain/utils.lua b/onchain/permissionless-arbitration/offchain/blockchain/utils.lua new file mode 100644 index 000000000..0d6fa0ae4 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/blockchain/utils.lua @@ -0,0 +1,22 @@ +local cast_advance_template = [[ +cast rpc -r "%s" evm_increaseTime %d +]] + +function advance_time(seconds, endpoint) + local cmd = string.format( + cast_advance_template, + endpoint, + seconds + ) + + local handle = io.popen(cmd) + assert(handle) + local ret = handle:read "*a" + handle:close() + + if ret:find "Error" then + error(string.format("Advance time `%d`s failed:\n%s", seconds, ret)) + end +end + +return { advance_time = advance_time } diff --git a/onchain/permissionless-arbitration/offchain/entrypoint.lua b/onchain/permissionless-arbitration/offchain/entrypoint.lua index b074484ab..68e88b21b 100755 --- a/onchain/permissionless-arbitration/offchain/entrypoint.lua +++ b/onchain/permissionless-arbitration/offchain/entrypoint.lua @@ -4,11 +4,16 @@ package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" local machine_path = "offchain/program/simple-program" +local FF_TIME = 30 +local IDLE_LIMIT = 5 +local INACTIVE_LIMIT = 10 local helper = require 'utils.helper' +local blockchain_utils = require "blockchain.utils" +local time = require "utils.time" +local blockchain_constants = require "blockchain.constants" local Blockchain = require "blockchain.node" local Machine = require "computation.machine" -local Client = require "blockchain.client" print "Hello, world!" os.execute "cd offchain/program && ./gen_machine_simple.sh" @@ -16,6 +21,7 @@ os.execute "cd offchain/program && ./gen_machine_simple.sh" local m = Machine:new_from_path(machine_path) local initial_hash = m:state().root_hash local blockchain = Blockchain:new() +time.sleep(2) local contract = blockchain:deploy_contract(initial_hash) -- add more player instances here @@ -42,7 +48,6 @@ setmetatable(pid_reader, { local no_active_players = 0 local all_idle = 0 -local client = Client:new(1) local last_ts = [[01/01/2000 00:00:00]] while true do local players = 0 @@ -70,10 +75,10 @@ while true do all_idle = 0 end - -- if all players are idle for 10 consecutive iterations, advance blockchain - if all_idle == 5 then - print("all players idle, fastforward blockchain for 30 seconds...") - client:advance_time(30) + -- if all players are idle for `IDLE_LIMIT` consecutive iterations, advance blockchain + if all_idle == IDLE_LIMIT then + print(string.format("all players idle, fastforward blockchain for %d seconds...", FF_TIME)) + blockchain_utils.advance_time(FF_TIME, blockchain_constants.endpoint) all_idle = 0 end end @@ -84,8 +89,8 @@ while true do no_active_players = 0 end - -- if no active player processes for 10 consecutive iterations, break loop - if no_active_players == 10 then + -- if no active player processes for `INACTIVE_LIMIT` consecutive iterations, break loop + if no_active_players == INACTIVE_LIMIT then print("no active players, end program...") break end diff --git a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua index d90e8654e..219fb6b57 100755 --- a/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/dishonest_player.lua @@ -3,26 +3,38 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -local Player = require "player.state" -local Client = require "blockchain.client" +local State = require "player.state" local Hash = require "cryptography.hash" +local Sender = require "blockchain.sender" +local HonestStrategy = require "player.honest_strategy" local time = require "utils.time" -local strategy = require "player.strategy" +local helper = require 'utils.helper' local player_index = tonumber(arg[1]) local tournament = arg[2] local machine_path = arg[3] local initial_hash = Hash:from_digest_hex(arg[4]) -local p + +local state = State:new(tournament) +local sender = Sender:new(player_index) +local honest_strategy do local FakeCommitmentBuilder = require "computation.fake_commitment" local builder = FakeCommitmentBuilder:new(initial_hash) - p = Player:new(tournament, player_index, builder, machine_path) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) end while true do - p:fetch() - if strategy.react_honestly(p) then break end + state:fetch() + local tx_count = sender.tx_count + if honest_strategy:react(state) then break end + -- player is considered idle if no tx sent in current iteration + if tx_count == sender.tx_count then + helper.log(player_index, "player idling") + helper.touch_player_idle(player_index) + else + helper.rm_player_idle(player_index) + end time.sleep(1) end diff --git a/onchain/permissionless-arbitration/offchain/player/honest_player.lua b/onchain/permissionless-arbitration/offchain/player/honest_player.lua index 856547297..36673b8cd 100755 --- a/onchain/permissionless-arbitration/offchain/player/honest_player.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_player.lua @@ -3,25 +3,37 @@ package.path = package.path .. ";/opt/cartesi/lib/lua/5.4/?.lua" package.path = package.path .. ";./offchain/?.lua" package.cpath = package.cpath .. ";/opt/cartesi/lib/lua/5.4/?.so" -local Player = require "player.state" -local Client = require "blockchain.client" +local State = require "player.state" local Hash = require "cryptography.hash" +local HonestStrategy = require "player.honest_strategy" +local Sender = require "blockchain.sender" local time = require "utils.time" -local strategy = require "player.strategy" +local helper = require 'utils.helper' local player_index = tonumber(arg[1]) local tournament = arg[2] local machine_path = arg[3] -local p + +local state = State:new(tournament) +local sender = Sender:new(player_index) +local honest_strategy do local CommitmentBuilder = require "computation.commitment" local builder = CommitmentBuilder:new(machine_path) - p = Player:new(tournament, player_index, builder, machine_path) + honest_strategy = HonestStrategy:new(builder, machine_path, sender) end while true do - p:fetch() - if strategy.react_honestly(p) then break end + state:fetch() + local tx_count = sender.tx_count + if honest_strategy:react(state) then break end + -- player is considered idle if no tx sent in current iteration + if tx_count == sender.tx_count then + helper.log(player_index, "player idling") + helper.touch_player_idle(player_index) + else + helper.rm_player_idle(player_index) + end time.sleep(1) end diff --git a/onchain/permissionless-arbitration/offchain/player/strategy.lua b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua similarity index 56% rename from onchain/permissionless-arbitration/offchain/player/strategy.lua rename to onchain/permissionless-arbitration/offchain/player/honest_strategy.lua index c48cf0cf3..73c984d18 100644 --- a/onchain/permissionless-arbitration/offchain/player/strategy.lua +++ b/onchain/permissionless-arbitration/offchain/player/honest_strategy.lua @@ -3,67 +3,72 @@ local helper = require 'utils.helper' local Machine = require "computation.machine" -local _react_match_honestly -local _react_tournament_honestly - -local function _join_tournament_if_needed(player, tournament) - if tournament.commitment_status.clock.allowance == 0 then - local f, left, right = tournament.commitment:children(tournament.commitment.root_hash) - assert(f) - local last, proof = tournament.commitment:last() - - helper.log(player.player_index, string.format( - "join tournament %s of level %d with commitment %s", - tournament.address, - tournament.level, - tournament.commitment.root_hash +local HonestStrategy = {} +HonestStrategy.__index = HonestStrategy + +function HonestStrategy:new(commitment_builder, machine_path, sender) + local honest_strategy = { + commitment_builder = commitment_builder, + machine_path = machine_path, + sender = sender + } + + setmetatable(honest_strategy, self) + return honest_strategy +end + +function HonestStrategy:_join_tournament(state, tournament, commitment) + local f, left, right = commitment:children(commitment.root_hash) + assert(f) + local last, proof = commitment:last() + + helper.log(self.sender.index, string.format( + "join tournament %s of level %d with commitment %s", + tournament.address, + tournament.level, + commitment.root_hash + )) + local ok, e = self.sender:tx_join_tournament( + tournament.address, + last, + proof, + left, + right + ) + if not ok then + helper.log(self.sender.index, string.format( + "join tournament reverted: %s", + e )) - local ok, e = pcall(player.client.tx_join_tournament, - player.client, - tournament.address, - last, - proof, - left, - right - ) - if not ok then - helper.log(player.player_index, string.format( - "join tournament reverted: %s", - e - )) - end - else - helper.touch_player_idle(player.player_index) end end -_react_match_honestly = function(player, match, commitment) +function HonestStrategy:_react_match(state, match, commitment) -- TODO call timeout if needed - helper.log(player.player_index, "HEIGHT: " .. match.current_height) + helper.log(self.sender.index, "Enter match at HEIGHT: " .. match.current_height) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then local f, left, right = commitment.root_hash:children() assert(f) - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "Calculating access logs for step %s", match.running_leaf )) local cycle = (match.running_leaf >> constants.log2_uarch_span):touinteger() local ucycle = (match.running_leaf & constants.uarch_span):touinteger() - local logs = Machine:get_logs(player.machine_path, cycle, ucycle) + local logs = Machine:get_logs(self.machine_path, cycle, ucycle) - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, commitment.root_hash )) - local ok, e = pcall(player.client.tx_win_leaf_match, - player.client, + local ok, e = self.sender:tx_win_leaf_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -72,19 +77,18 @@ _react_match_honestly = function(player, match, commitment) logs ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win leaf match reverted: %s", e )) end - else - return _react_tournament_honestly(player, match.inner_tournament) + elseif match.inner_tournament then + return self:_react_tournament(state, match.inner_tournament) end elseif match.current_height == 1 then -- match to be sealed local found, left, right = match.current_other_parent:children() if not found then - helper.touch_player_idle(player.player_index) return end @@ -96,14 +100,13 @@ _react_match_honestly = function(player, match, commitment) end if match.tournament.level == 1 then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal leaf match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, commitment.root_hash )) - local ok, e = pcall(player.client.tx_seal_leaf_match, - player.client, + local ok, e = self.sender:tx_seal_leaf_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -113,20 +116,19 @@ _react_match_honestly = function(player, match, commitment) proof ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal leaf match reverted: %s", e )) end else - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal inner match in tournament %s of level %d for commitment %s", match.tournament.address, match.tournament.level, commitment.root_hash )) - local ok, e = pcall(player.client.tx_seal_inner_match, - player.client, + local ok, e = self.sender:tx_seal_inner_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -136,7 +138,7 @@ _react_match_honestly = function(player, match, commitment) proof ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "seal inner match reverted: %s", e )) @@ -146,7 +148,6 @@ _react_match_honestly = function(player, match, commitment) -- match running local found, left, right = match.current_other_parent:children() if not found then - helper.touch_player_idle(player.player_index) return end @@ -161,15 +162,14 @@ _react_match_honestly = function(player, match, commitment) assert(f) end - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "advance match with current height %d in tournament %s of level %d for commitment %s", match.current_height, match.tournament.address, match.tournament.level, commitment.root_hash )) - local ok, e = pcall(player.client.tx_advance_match, - player.client, + local ok, e = self.sender:tx_advance_match( match.tournament.address, match.commitment_one, match.commitment_two, @@ -179,7 +179,7 @@ _react_match_honestly = function(player, match, commitment) new_right ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "advance match reverted: %s", e )) @@ -187,45 +187,52 @@ _react_match_honestly = function(player, match, commitment) end end -_react_tournament_honestly = function(player, tournament) +function HonestStrategy:_react_tournament(state, tournament) + helper.log(self.sender.index, "Enter tournament at address: " .. tournament.address) + local commitment = self.commitment_builder:build( + tournament.base_big_cycle, + tournament.level + ) + local tournament_winner = tournament.tournament_winner if tournament_winner[1] == "true" then if not tournament.parent then - helper.log(player.player_index, "TOURNAMENT FINISHED, HURRAYYY") - helper.log(player.player_index, "Winner commitment: " .. tournament_winner[2]:hex_string()) - helper.log(player.player_index, "Final state: " .. tournament_winner[3]:hex_string()) + helper.log(self.sender.index, "TOURNAMENT FINISHED, HURRAYYY") + helper.log(self.sender.index, "Winner commitment: " .. tournament_winner[2]:hex_string()) + helper.log(self.sender.index, "Final state: " .. tournament_winner[3]:hex_string()) return true else - local old_commitment = tournament.parent.commitment + local old_commitment = self.commitment_builder:build( + tournament.parent.base_big_cycle, + tournament.parent.level + ) if tournament_winner[2] ~= old_commitment.root_hash then - helper.log(player.player_index, "player lost tournament") - player.has_lost = true - return + helper.log(self.sender.index, "player lost tournament") + return true end - if tournament.called_win then - helper.log(player.player_index, "player already called winInnerMatch") + if tournament.commitments[commitment.root_hash].called_win then + helper.log(self.sender.index, "player already called winInnerMatch") return else - tournament.called_win = true + tournament.commitments[commitment.root_hash].called_win = true end - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win tournament %s of level %d for commitment %s", tournament.address, tournament.level, - tournament.commitment.root_hash + commitment.root_hash )) local _, left, right = old_commitment:children(old_commitment.root_hash) - local ok, e = pcall(player.client.tx_win_inner_match, - player.client, + local ok, e = self.sender:tx_win_inner_match( tournament.parent.address, tournament.address, left, right ) if not ok then - helper.log(player.player_index, string.format( + helper.log(self.sender.index, string.format( "win inner match reverted: %s", e )) @@ -234,20 +241,18 @@ _react_tournament_honestly = function(player, tournament) end end - if not tournament.latest_match then - _join_tournament_if_needed(player, tournament) + if not tournament.commitments[commitment.root_hash] then + self:_join_tournament(state, tournament, commitment) else - _react_match_honestly(player, tournament.latest_match, tournament.commitment) + local latest_match = tournament.commitments[commitment.root_hash].latest_match + if latest_match then + return self:_react_match(state, latest_match, commitment) + end end end -local function _react_honestly(player) - if player.has_lost then - return true - end - return _react_tournament_honestly(player, player.root_tournament) +function HonestStrategy:react(state) + return self:_react_tournament(state, state.root_tournament) end -return { - react_honestly = _react_honestly -} +return HonestStrategy diff --git a/onchain/permissionless-arbitration/offchain/player/state.lua b/onchain/permissionless-arbitration/offchain/player/state.lua index de1a2d5fa..7f009538e 100644 --- a/onchain/permissionless-arbitration/offchain/player/state.lua +++ b/onchain/permissionless-arbitration/offchain/player/state.lua @@ -1,77 +1,72 @@ local constants = require "constants" local bint = require 'utils.bint' (256) -- use 256 bits integers -local Client = require "blockchain.client" +local Reader = require "blockchain.reader" -local Player = {} -Player.__index = Player +local State = {} +State.__index = State -function Player:new(root_tournament_address, player_index, commitment_builder, machine_path) - local player = { - machine_path = machine_path, +function State:new(root_tournament_address) + local state = { root_tournament = { base_big_cycle = 0, address = root_tournament_address, level = constants.levels, parent = false, - commitment = false, - commitment_status = {}, - tournament_winner = {}, - latest_match = {}, + commitments = {}, + matches = {}, + tournament_winner = {} }, - client = Client:new(player_index), - commitment_builder = commitment_builder, - player_index = player_index + reader = Reader:new() } - setmetatable(player, self) - return player + setmetatable(state, self) + return state end -function Player:fetch() +function State:fetch() return self:_fetch_tournament(self.root_tournament) end -function Player:_fetch_tournament(tournament) - local commitment = tournament.commitment - if not commitment then - commitment = self.commitment_builder:build( - tournament.base_big_cycle, - tournament.level - ) - tournament.commitment = commitment - end +function State:_fetch_tournament(tournament) + local matches = self:_matches(tournament) + local commitments = self.reader:read_commitment_joined(tournament.address) - if not tournament.parent then - tournament.tournament_winner = self.client:root_tournament_winner(tournament.address) - else - tournament.tournament_winner = self.client:inner_tournament_winner(tournament.address) + for _, log in ipairs(commitments) do + local root = log.root + local status = self.reader:read_commitment(tournament.address, root) + tournament.commitments[root] = { status = status, latest_match = false } end - tournament.latest_match = self:_latest_match(tournament) + for _, match in ipairs(matches) do + if match then + self:_fetch_match(match) + tournament.commitments[match.commitment_one].latest_match = match + tournament.commitments[match.commitment_two].latest_match = match + end + end + tournament.matches = matches - if not tournament.latest_match then - tournament.commitment_status = self.client:read_commitment(tournament.address, commitment.root_hash) + if not tournament.parent then + tournament.tournament_winner = self.reader:root_tournament_winner(tournament.address) else - self:_fetch_match(tournament.latest_match, commitment) + tournament.tournament_winner = self.reader:inner_tournament_winner(tournament.address) end end -function Player:_fetch_match(match, commitment) +function State:_fetch_match(match) if match.current_height == 0 then -- match sealed if match.tournament.level == 1 then - local f, left, right = commitment.root_hash:children() - assert(f) match.finished = - self.client:match(match.tournament.address, match.match_id_hash)[1]:is_zero() + self.reader:match(match.tournament.address, match.match_id_hash)[1]:is_zero() if match.finished then - match.delay = tonumber(self.client:maximum_delay(match.tournament.address)[1]) + match.delay = tonumber(self.reader:maximum_delay(match.tournament.address)[1]) end else - local address = self.client:read_tournament_created( + local address = self.reader:read_tournament_created( match.tournament.address, match.match_id_hash ).new_tournament @@ -81,6 +76,7 @@ function Player:_fetch_match(match, commitment) new_tournament.level = match.tournament.level - 1 new_tournament.parent = match.tournament new_tournament.base_big_cycle = match.base_big_cycle + new_tournament.commitments = {} match.inner_tournament = new_tournament return self:_fetch_tournament(new_tournament) @@ -88,32 +84,31 @@ function Player:_fetch_match(match, commitment) end end -function Player:_latest_match(tournament) - local commitment = tournament.commitment - local matches = self.client:read_match_created(tournament.address, commitment.root_hash) - local last_match = matches[#matches] - - if not last_match then return false end +function State:_matches(tournament) + local matches = self.reader:read_match_created(tournament.address) - local m = self.client:match(tournament.address, last_match.match_id_hash) - if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then - return false + for k, match in ipairs(matches) do + local m = self.reader:match(tournament.address, match.match_id_hash) + if m[1]:is_zero() and m[2]:is_zero() and m[3]:is_zero() then + matches[k] = false + else + match.current_other_parent = m[1] + match.current_left = m[2] + match.current_right = m[3] + match.running_leaf = bint(m[4]) + match.current_height = tonumber(m[5]) + match.level = tonumber(m[6]) + match.tournament = tournament + + local level = tournament.level + local base = bint(tournament.base_big_cycle) + local step = bint(1) << constants.log2step[level] + match.leaf_cycle = base + (step * match.running_leaf) + match.base_big_cycle = (match.leaf_cycle >> constants.log2_uarch_span):touinteger() + end end - last_match.current_other_parent = m[1] - last_match.current_left = m[2] - last_match.current_right = m[3] - last_match.running_leaf = bint(m[4]) - last_match.current_height = tonumber(m[5]) - last_match.level = tonumber(m[6]) - last_match.tournament = tournament - - local level = tournament.level - local base = bint(tournament.base_big_cycle) - local step = bint(1) << constants.log2step[level] - last_match.leaf_cycle = base + (step * last_match.running_leaf) - last_match.base_big_cycle = (last_match.leaf_cycle >> constants.log2_uarch_span):touinteger() - - return last_match + + return matches end -return Player +return State diff --git a/onchain/permissionless-arbitration/offchain/utils/helper.lua b/onchain/permissionless-arbitration/offchain/utils/helper.lua index 6dca7d413..8410296b8 100644 --- a/onchain/permissionless-arbitration/offchain/utils/helper.lua +++ b/onchain/permissionless-arbitration/offchain/utils/helper.lua @@ -1,7 +1,7 @@ local color = require "utils.color" local names = {'green', 'yellow', 'blue', 'pink', 'cyan', 'white'} -local idle_template = [[ls player%d_idle 2> /dev/null | grep player%d_idle | wc -l]] +local idle_template = [[ls player%d_idle 2>/dev/null | grep player%d_idle | wc -l]] local ps_template = [[ps %s | grep defunct | wc -l]] local function log(player_index, msg) @@ -63,7 +63,7 @@ local function is_player_idle(player_index) end local function rm_player_idle(player_index) - os.execute(string.format("rm player%d_idle", player_index)) + os.execute(string.format("rm player%d_idle 2>/dev/null", player_index)) end local function all_players_idle(pid_player) @@ -89,5 +89,6 @@ return { stop_players = stop_players, touch_player_idle = touch_player_idle, all_players_idle = all_players_idle, - rm_all_players_idle = rm_all_players_idle + rm_all_players_idle = rm_all_players_idle, + rm_player_idle = rm_player_idle } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index bd75fcff9..3874fe0e2 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -67,6 +67,7 @@ abstract contract Tournament { Tree.Node indexed two, Tree.Node leftOfTwo ); + event commitmentJoined(Tree.Node root); // @@ -148,6 +149,7 @@ abstract contract Tournament { _clock.setNewPaused(startInstant, allowance); pairCommitment(_commitmentRoot, _clock, _leftNode, _rightNode); + emit commitmentJoined(_commitmentRoot); } /// @notice Advance the match until the smallest divergence is found at current level