From 7d806a5564a17c433e231c1b9b144d8512cd23b3 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 6 Apr 2026 13:41:28 -0300 Subject: [PATCH 001/113] feat: avoid reentrancy attacks on PRT --- prt/contracts/src/tournament/Tournament.sol | 30 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/prt/contracts/src/tournament/Tournament.sol b/prt/contracts/src/tournament/Tournament.sol index d40adc333..0c3be714e 100644 --- a/prt/contracts/src/tournament/Tournament.sol +++ b/prt/contracts/src/tournament/Tournament.sol @@ -107,10 +107,20 @@ contract Tournament is ITournament { _; } + /// @notice Acquires the lock at the start, + /// and releases the lock at the end. + /// Avoids reentrancy attacks. + modifier withLock() { + _acquireLock(); + _; + _releaseLock(); + } + /// @notice Refunds the message sender with the amount /// of Ether wasted on gas on this function call plus /// a profit, capped by the current contract balance /// and a fraction of the bond value. + /// Also acquires the lock beforehand and releases it afterward. /// @param gasEstimate A worst-case gas estimate for the modified function /// forge-lint: disable-next-line(unwrapped-modifier-logic) modifier refundable(uint256 gasEstimate) { @@ -224,7 +234,7 @@ contract Tournament is ITournament { bytes32[] calldata _proof, Tree.Node _leftNode, Tree.Node _rightNode - ) external payable override tournamentOpen { + ) external payable override withLock tournamentOpen { require(msg.value >= bondValue(), InsufficientBond()); Tree.Node _commitmentRoot = _leftNode.join(_rightNode); @@ -359,7 +369,7 @@ contract Tournament is ITournament { /// * Winner is the root tournament winner. /// - NON-ROOT: /// * Winner is the inner winner that will be used by the parent tournament. - function tryRecoveringBond() public override returns (bool) { + function tryRecoveringBond() public override withLock returns (bool) { require(isFinished(), TournamentNotFinished()); (bool hasDangling, Tree.Node winningCommitment) = @@ -372,6 +382,10 @@ contract Tournament is ITournament { uint256 contractBalance = address(this).balance; (bool success,) = winner.call{value: contractBalance}(""); + // This is the only part of the function body that is not + // compliant to the checks-effects-interactions pattern. + // So, in order to avoid reentrancy attacks, this function + // is modified to acquire (and release) the lock. if (success) { deleteClaimer(winningCommitment); } @@ -943,9 +957,17 @@ contract Tournament is ITournament { return a.min(b).min(c); } - function _refundableBefore() private returns (uint256 gasBefore) { + function _acquireLock() private { require(!locked, ReentrancyDetected()); locked = true; + } + + function _releaseLock() private { + locked = false; + } + + function _refundableBefore() private returns (uint256 gasBefore) { + _acquireLock(); gasBefore = gasleft(); } @@ -963,7 +985,7 @@ contract Tournament is ITournament { msg.sender.call{value: refundValue}(""); emit PartialBondRefund(msg.sender, refundValue, status, ret); - locked = false; + _releaseLock(); } /// @inheritdoc ITournament From 66f1014b839749bb3c7a1af299c3793a581a1903 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 08:43:07 -0300 Subject: [PATCH 002/113] docs: add NatSpec to ITournament errors --- prt/contracts/src/ITournament.sol | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index e26d86155..91ef0ec29 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -188,42 +188,152 @@ interface ITournament { // Errors // + /// @notice The amount of Wei passed to `joinTournament` is less than + /// the bond value (which can be consulted through `bondValue`). error InsufficientBond(); + + /// @notice A bond refund cannot be issued to the tournament winner + /// because there is no tournament winner. error NoWinner(); + + /// @notice The divergence falls in the first leaf node of the commitment tree + /// (in the granularity of the given tournament level), and the state prior + /// to the divergence (provided by the player) is not equal to the agreed-upon + /// initial machine state (set forth by the tournament instantiator). + /// @param initialState The agreed-upon initial machine state + /// @param agreeState The state prior to the divergence provided by the player error IncorrectAgreeState( Machine.Hash initialState, Machine.Hash agreeState ); + error LengthMismatch(uint64 treeHeight, uint64 siblingsLength); + + /// @notice A player provided a commitment leaf-node proof that produced + /// a commitment root different from the one provided to `joinTournament`. + /// @param received The expected commitment root + /// @param expected The commitment root computed from the leaf-node proof error CommitmentStateMismatch(Tree.Node received, Tree.Node expected); + error CommitmentFinalStateMismatch(Tree.Node received, Tree.Node expected); + + /// @notice A player provided a commitment leaf-node proof whose length + /// is different from the commitment tree height. + /// @param received The agreed-upon commitment tree height + /// @param expected The length of the siblings array provided by the player error CommitmentProofWrongSize(uint256 received, uint256 expected); + + /// @notice The tournament is finished, which restricts most actions. error TournamentIsFinished(); + + /// @notice The tournament is not finished, which restricts bonds from + /// being recovered at this point, since a winner has not been declared yet. error TournamentNotFinished(); + + /// @notice The tournament is closed, which restricts new commitments + /// from joining the tournament, since the tournament's global allowance + /// has already elapsed. error TournamentIsClosed(); + + /// @notice A reentrancy-attack attempt has been detected and neutralized + /// by reverting the nested call to the tournament contract. error ReentrancyDetected(); + + /// @notice A player provided commitment root children nodes that produced + /// a commitment root different from the one provided to `joinTournament`. + /// This error is raised in the context of a match in which one of the + /// commitments has timed out, and the other hasn't, allowing it to be + /// paired against any dangling commitment (instantly) or challenging + /// commitment (that might join the tournament later, if still open). + /// @param commitment Which of the two commitments did not timeout (1 or 2) + /// @param parent The root of the commitment that did not timeout + /// @param left The commitment root left child provided by the player + /// @param right The commitment root right child provided by the player error WrongChildren( uint256 commitment, Tree.Node parent, Tree.Node left, Tree.Node right ); + + /// @notice A player tried to win a match by timeout but neither of the + /// two match commitment clocks have timed out yet. error ClockNotTimedOut(); + + /// @notice A player tried to eliminate a match by timeout but at + /// least one of the two match commitment clocks has not timed out yet. error BothClocksHaveNotTimedOut(); + + /// @notice A player tried to join the inner tournament with a commitment + /// whose final state is not equal to neither of the two contested final states + /// of the match in the parent tournament that created such inner tournament. + /// @param contestedFinalStateOne The contested final state #1 + /// @param contestedFinalStateTwo The contested final state #2 + /// @param finalState The final state of the commitment provided by the player error InvalidContestedFinalState( Machine.Hash contestedFinalStateOne, Machine.Hash contestedFinalStateTwo, Machine.Hash finalState ); + + /// @notice Internal error in which an invalid winner commitment value + /// is passed to an internal function that deletes a match. + /// @param winnerCommitment The invalid winner commitment value error InvalidWinnerCommitment(WinnerCommitment winnerCommitment); + + /// @notice The tournament has finished but with no winners. + /// This is unexpected to happen because we assume that at least + /// one player is actively defending the correct commitment. error TournamentFailedNoWinner(); + + /// @notice The child tournament has not yet finished, + /// and therefore not yet declared a winner. error ChildTournamentNotFinished(); + + /// @notice The child tournament cannot be eliminated, + /// either because it has not yet finished or + /// because the winner still has time to claim its victory. error ChildTournamentCannotBeEliminated(); + + /// @notice The child tournament cannot be won, + /// because it can be eliminated. error ChildTournamentMustBeEliminated(); + + /// @notice The player has provided commitment root children + /// whose parent is different from the commitment root that won + /// a child tournament. + /// @param commitmentRoot The commitment root provided by the player + /// @param winner The child-tournament winning commitment root error WrongTournamentWinner(Tree.Node commitmentRoot, Tree.Node winner); + + /// @notice The child tournament winning commitment root is + /// different from the two commitment roots that were paired + /// against each other in this tournament. + /// @param winner The child-tournament winning commitment root error InvalidTournamentWinner(Tree.Node winner); + + /// @notice The on-chain implementation of the state-transition + /// function applied over an agreed-upon state has produced a + /// post-state that differs from that of the match commitment. + /// @param commitment Which of the two commitments is wrong + /// @param computed The post-state computed by the state-transition function + /// @param claimed The post-state contained within the commitment error WrongFinalState( uint256 commitment, Machine.Hash computed, Machine.Hash claimed ); + + /// @notice While trying to win a match through the on-chain implementation + /// of the state-transition (step) function, a player has supplied the left + /// and right children of a commitment root that is different from + /// both commitment roots of a match. error WrongNodesForStep(); + + /// @notice A player has attempted to call a function that can only be + /// called for leaf tournaments (in which `level == levels - 1`). error RequireLeafTournament(); + + /// @notice A player has attempted to call a function that can only be + /// called for non-leaf tournaments (in which `0 <= level < levels - 1`). error RequireNonLeafTournament(); + + /// @notice A player has attempted to call a function that can only be + /// called for non-root tournaments (in which `0 < level <= levels - 1`). error RequireNonRootTournament(); // From 5336437579015b5294839a14d998796b0364d777 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 08:44:07 -0300 Subject: [PATCH 003/113] refactor: rename ITournament error parameters --- prt/contracts/src/ITournament.sol | 33 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index 91ef0ec29..f38a32703 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -210,17 +210,17 @@ interface ITournament { /// @notice A player provided a commitment leaf-node proof that produced /// a commitment root different from the one provided to `joinTournament`. - /// @param received The expected commitment root - /// @param expected The commitment root computed from the leaf-node proof - error CommitmentStateMismatch(Tree.Node received, Tree.Node expected); + /// @param expected The expected commitment root + /// @param computed The commitment root computed from the leaf-node proof + error CommitmentStateMismatch(Tree.Node expected, Tree.Node computed); error CommitmentFinalStateMismatch(Tree.Node received, Tree.Node expected); /// @notice A player provided a commitment leaf-node proof whose length /// is different from the commitment tree height. - /// @param received The agreed-upon commitment tree height - /// @param expected The length of the siblings array provided by the player - error CommitmentProofWrongSize(uint256 received, uint256 expected); + /// @param treeHeight The agreed-upon commitment tree height + /// @param siblingsLength The length of the siblings array provided by the player + error CommitmentProofWrongSize(uint256 treeHeight, uint256 siblingsLength); /// @notice The tournament is finished, which restricts most actions. error TournamentIsFinished(); @@ -244,12 +244,15 @@ interface ITournament { /// commitments has timed out, and the other hasn't, allowing it to be /// paired against any dangling commitment (instantly) or challenging /// commitment (that might join the tournament later, if still open). - /// @param commitment Which of the two commitments did not timeout (1 or 2) - /// @param parent The root of the commitment that did not timeout + /// @param whichCommitment Which of the two commitments did not timeout (1 or 2) + /// @param commitmentRoot The root of the commitment that did not timeout /// @param left The commitment root left child provided by the player /// @param right The commitment root right child provided by the player error WrongChildren( - uint256 commitment, Tree.Node parent, Tree.Node left, Tree.Node right + uint256 whichCommitment, + Tree.Node commitmentRoot, + Tree.Node left, + Tree.Node right ); /// @notice A player tried to win a match by timeout but neither of the @@ -311,11 +314,13 @@ interface ITournament { /// @notice The on-chain implementation of the state-transition /// function applied over an agreed-upon state has produced a /// post-state that differs from that of the match commitment. - /// @param commitment Which of the two commitments is wrong - /// @param computed The post-state computed by the state-transition function - /// @param claimed The post-state contained within the commitment + /// @param whichCommitment Which of the two commitments is wrong + /// @param computedPostState The post-state computed by the state-transition function + /// @param committedPostState The post-state contained within the commitment error WrongFinalState( - uint256 commitment, Machine.Hash computed, Machine.Hash claimed + uint256 whichCommitment, + Machine.Hash computedPostState, + Machine.Hash committedPostState ); /// @notice While trying to win a match through the on-chain implementation @@ -577,7 +582,7 @@ interface ITournament { /// @notice Get the clock and final state of a commitment. /// @param commitmentRoot The commitment /// @return clock The commitment clock - /// @return finalState The commited final state + /// @return finalState The committed final state function getCommitment(Tree.Node commitmentRoot) external view From 140dd50e48e16ae9ae819928228c0f2510991f4a Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 08:45:01 -0300 Subject: [PATCH 004/113] refactor!: rename ITournament errors --- prt/contracts/src/ITournament.sol | 4 ++-- prt/contracts/src/tournament/Tournament.sol | 4 ++-- prt/contracts/test/BottomTournament.t.sol | 2 +- prt/contracts/test/MiddleTournament.t.sol | 6 +++--- prt/contracts/test/Tournament.t.sol | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index f38a32703..6096407c9 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -257,11 +257,11 @@ interface ITournament { /// @notice A player tried to win a match by timeout but neither of the /// two match commitment clocks have timed out yet. - error ClockNotTimedOut(); + error NeitherClockHasTimedOut(); /// @notice A player tried to eliminate a match by timeout but at /// least one of the two match commitment clocks has not timed out yet. - error BothClocksHaveNotTimedOut(); + error AtLeastOneClockHasNotTimedOut(); /// @notice A player tried to join the inner tournament with a commitment /// whose final state is not equal to neither of the two contested final states diff --git a/prt/contracts/src/tournament/Tournament.sol b/prt/contracts/src/tournament/Tournament.sol index 0c3be714e..9cc367735 100644 --- a/prt/contracts/src/tournament/Tournament.sol +++ b/prt/contracts/src/tournament/Tournament.sol @@ -332,7 +332,7 @@ contract Tournament is ITournament { _matchId, MatchDeletionReason.TIMEOUT, WinnerCommitment.TWO ); } else { - revert ClockNotTimedOut(); + revert NeitherClockHasTimedOut(); } } @@ -359,7 +359,7 @@ contract Tournament is ITournament { _matchId, MatchDeletionReason.TIMEOUT, WinnerCommitment.NONE ); } else { - revert BothClocksHaveNotTimedOut(); + revert AtLeastOneClockHasNotTimedOut(); } } diff --git a/prt/contracts/test/BottomTournament.t.sol b/prt/contracts/test/BottomTournament.t.sol index d8f49eef5..196170a3c 100644 --- a/prt/contracts/test/BottomTournament.t.sol +++ b/prt/contracts/test/BottomTournament.t.sol @@ -521,7 +521,7 @@ contract BottomTournamentTest is Util { assertFalse(c2.startInstant.isZero(), "c2 should be running"); // Elimination should fail immediately after seal (both have time left) - vm.expectRevert(ITournament.BothClocksHaveNotTimedOut.selector); + vm.expectRevert(ITournament.AtLeastOneClockHasNotTimedOut.selector); bottomTournament.eliminateMatchByTimeout(_matchId); // Fast-forward to when both clocks are exhausted and eliminate diff --git a/prt/contracts/test/MiddleTournament.t.sol b/prt/contracts/test/MiddleTournament.t.sol index e32679c52..10520bf62 100644 --- a/prt/contracts/test/MiddleTournament.t.sol +++ b/prt/contracts/test/MiddleTournament.t.sol @@ -284,7 +284,7 @@ contract MiddleTournamentTest is Util { _match = middleTournament.getMatch(_matchId.hashFromId()); assertTrue(_match.exists(), "match should exist"); - vm.expectRevert(ITournament.ClockNotTimedOut.selector); + vm.expectRevert(ITournament.NeitherClockHasTimedOut.selector); middleTournament.winMatchByTimeout( _matchId, playerNodes[1][ArbitrationConstants.height(1) - 1], @@ -589,7 +589,7 @@ contract MiddleTournamentTest is Util { assertFalse(hasWinner); vm.roll(vm.getBlockNumber() + Time.Duration.unwrap(MAX_ALLOWANCE) - 1); - vm.expectRevert(ITournament.ClockNotTimedOut.selector); + vm.expectRevert(ITournament.NeitherClockHasTimedOut.selector); middleTournament.winMatchByTimeout( Util.matchId(1, 1), playerNodes[0][ArbitrationConstants.height(1) - 1], @@ -661,7 +661,7 @@ contract MiddleTournamentTest is Util { ); vm.roll(vm.getBlockNumber() + Time.Duration.unwrap(MATCH_EFFORT)); - vm.expectRevert(ITournament.ClockNotTimedOut.selector); + vm.expectRevert(ITournament.NeitherClockHasTimedOut.selector); topTournament.winMatchByTimeout( topMatch, playerNodes[0][ArbitrationConstants.height(0) - 1], diff --git a/prt/contracts/test/Tournament.t.sol b/prt/contracts/test/Tournament.t.sol index b66917116..75c943b3e 100644 --- a/prt/contracts/test/Tournament.t.sol +++ b/prt/contracts/test/Tournament.t.sol @@ -291,7 +291,7 @@ contract TournamentTest is Util { vm.roll(_rootTournamentFinish - 1); // cannot eliminate match when both blocks still have time - vm.expectRevert(ITournament.BothClocksHaveNotTimedOut.selector); + vm.expectRevert(ITournament.AtLeastOneClockHasNotTimedOut.selector); topTournament.eliminateMatchByTimeout(_matchId); vm.roll(_rootTournamentFinish); From 071b79a0ecd079c78ae0eebe8561ea77a8624125 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 08:45:22 -0300 Subject: [PATCH 005/113] refactor!: deduplicate ITournament errors --- prt/contracts/src/ITournament.sol | 4 ---- prt/contracts/src/tournament/libs/Commitment.sol | 16 +++++++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index 6096407c9..0b526f16f 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -206,16 +206,12 @@ interface ITournament { Machine.Hash initialState, Machine.Hash agreeState ); - error LengthMismatch(uint64 treeHeight, uint64 siblingsLength); - /// @notice A player provided a commitment leaf-node proof that produced /// a commitment root different from the one provided to `joinTournament`. /// @param expected The expected commitment root /// @param computed The commitment root computed from the leaf-node proof error CommitmentStateMismatch(Tree.Node expected, Tree.Node computed); - error CommitmentFinalStateMismatch(Tree.Node received, Tree.Node expected); - /// @notice A player provided a commitment leaf-node proof whose length /// is different from the commitment tree height. /// @param treeHeight The agreed-upon commitment tree height diff --git a/prt/contracts/src/tournament/libs/Commitment.sol b/prt/contracts/src/tournament/libs/Commitment.sol index bcb5e24f4..30d3f2f9b 100644 --- a/prt/contracts/src/tournament/libs/Commitment.sol +++ b/prt/contracts/src/tournament/libs/Commitment.sol @@ -38,13 +38,13 @@ library Commitment { Machine.Hash state, bytes32[] calldata hashProof ) internal pure { - Tree.Node expectedCommitment = getRoot( + Tree.Node computedCommitment = getRoot( Machine.Hash.unwrap(state), treeHeight, position, hashProof ); require( - commitment.eq(expectedCommitment), - ITournament.CommitmentStateMismatch(commitment, expectedCommitment) + commitment.eq(computedCommitment), + ITournament.CommitmentStateMismatch(commitment, computedCommitment) ); } @@ -61,7 +61,7 @@ library Commitment { uint64 siblingsLength = uint64(siblings.length); require( treeHeight == siblingsLength, - ITournament.LengthMismatch(treeHeight, siblingsLength) + ITournament.CommitmentProofWrongSize(treeHeight, siblingsLength) ); for (uint256 i = 0; i < treeHeight; i++) { @@ -81,16 +81,14 @@ library Commitment { Machine.Hash finalState, bytes32[] calldata hashProof ) internal pure { - Tree.Node expectedCommitment = + Tree.Node computedCommitment = getRootForLastLeaf( treeHeight, Machine.Hash.unwrap(finalState), hashProof ); require( - commitment.eq(expectedCommitment), - ITournament.CommitmentFinalStateMismatch( - commitment, expectedCommitment - ) + commitment.eq(computedCommitment), + ITournament.CommitmentStateMismatch(commitment, computedCommitment) ); } From 308ef68446a44b5c69f7188c680679bd07fdf002 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 08:45:42 -0300 Subject: [PATCH 006/113] refactor: call Tournament.canBeEliminated internally --- prt/contracts/src/tournament/Tournament.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prt/contracts/src/tournament/Tournament.sol b/prt/contracts/src/tournament/Tournament.sol index 9cc367735..d0eeefb8b 100644 --- a/prt/contracts/src/tournament/Tournament.sol +++ b/prt/contracts/src/tournament/Tournament.sol @@ -997,7 +997,7 @@ contract Tournament is ITournament { /// 1. Tournament finished and has no winner, OR /// 2. Tournament finished and enough time elapsed after the winning /// commitment could have won (winner's allowance window). - function canBeEliminated() external view override returns (bool) { + function canBeEliminated() public view override returns (bool) { TournamentArguments memory args = tournamentArguments(); if (_isRootTournament(args)) { @@ -1042,7 +1042,7 @@ contract Tournament is ITournament { revert RequireNonRootTournament(); } - if (!isFinished() || this.canBeEliminated()) { + if (!isFinished() || canBeEliminated()) { Clock.State memory zeroClock; return (false, Tree.ZERO_NODE, Tree.ZERO_NODE, zeroClock); } From af96bcbc4ad1f68f47e9a02189b914194c321bf7 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 09:35:02 -0300 Subject: [PATCH 007/113] refactor!: define custom errors for Clock lib --- prt/contracts/src/ITournament.sol | 21 +++++++++++++++++++++ prt/contracts/src/tournament/libs/Clock.sol | 12 +++++++----- prt/contracts/test/Clock.t.sol | 9 ++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index 0b526f16f..948d74703 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -337,6 +337,27 @@ interface ITournament { /// called for non-root tournaments (in which `0 < level <= levels - 1`). error RequireNonRootTournament(); + /// @notice A clock was expected to be initialized, but isn't. + error ClockNotInitialized(); + + /// @notice A clock wasn't expected to be initialized, but is. + error ClockAlreadyInitialized(); + + /// @notice The time until timeout of a paused clock is consulted, + /// but cannot be determined, since we cannot know if or when + /// the clock will be unpaused. + error PausedClockCannotTimeout(); + + /// @notice There is an attempt to advance a clock with no time left, + /// but doing so would result in a clock with zero allowance, + /// which is used to indicate that such a clock is not initialized. + error CannotAdvanceTimedOutClock(); + + /// @notice There is an attempt to initialize a clock with zero allowance, + /// but doing so would contradict the semantics of the action, because + /// zero allowance is used to indicate that such a clock is not initialized. + error InitializedClockCannotHaveZeroAllowance(); + // // Functions // diff --git a/prt/contracts/src/tournament/libs/Clock.sol b/prt/contracts/src/tournament/libs/Clock.sol index af566f3f7..5b9a3a6be 100644 --- a/prt/contracts/src/tournament/libs/Clock.sol +++ b/prt/contracts/src/tournament/libs/Clock.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.17; +import {ITournament} from "prt-contracts/ITournament.sol"; + import {Time} from "./Time.sol"; library Clock { @@ -24,11 +26,11 @@ library Clock { } function requireInitialized(State memory state) internal pure { - require(!state.notInitialized(), "clock is not initialized"); + require(!state.notInitialized(), ITournament.ClockNotInitialized()); } function requireNotInitialized(State memory state) internal pure { - require(state.notInitialized(), "clock is initialized"); + require(state.notInitialized(), ITournament.ClockAlreadyInitialized()); } function hasTimeLeft(State memory state) internal view returns (bool) { @@ -62,7 +64,7 @@ library Clock { returns (Time.Duration) { if (state.startInstant.isZero()) { - revert("a paused clock can't timeout"); + revert ITournament.PausedClockCannotTimeout(); } return Time.timeSpan(Time.currentTime(), state.startInstant) @@ -109,7 +111,7 @@ library Clock { Time.Duration _timeLeft = timeLeft(state); if (_timeLeft.isZero()) { - revert("can't advance clock with no time left"); + revert ITournament.CannotAdvanceTimedOutClock(); } toggleClock(state); @@ -170,7 +172,7 @@ library Clock { private { if (allowance.isZero()) { - revert("can't create clock with zero time"); + revert ITournament.InitializedClockCannotHaveZeroAllowance(); } state.allowance = allowance; diff --git a/prt/contracts/test/Clock.t.sol b/prt/contracts/test/Clock.t.sol index bd413e920..d906237ea 100644 --- a/prt/contracts/test/Clock.t.sol +++ b/prt/contracts/test/Clock.t.sol @@ -14,6 +14,7 @@ pragma solidity ^0.8.0; import {Test} from "forge-std-1.9.6/src/Test.sol"; +import {ITournament} from "src/ITournament.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Time} from "src/tournament/libs/Time.sol"; @@ -92,7 +93,9 @@ contract ClockTest is Test { } function testNewClock() public { - vm.expectRevert("can't create clock with zero time"); + vm.expectRevert( + ITournament.InitializedClockCannotHaveZeroAllowance.selector + ); ExternalClock.setNewPaused( clock2, Time.currentTime(), Time.Duration.wrap(0) ); @@ -110,7 +113,7 @@ contract ClockTest is Test { vm.roll(vm.getBlockNumber() + CLOCK_1_ALLOWANCE); assertTrue(!clock1.hasTimeLeft(), "clock1 should run out of time"); - vm.expectRevert("can't advance clock with no time left"); + vm.expectRevert(ITournament.CannotAdvanceTimedOutClock.selector); ExternalClock.advanceClock(clock1); } @@ -156,7 +159,7 @@ contract ClockTest is Test { "clock2 shouldn be timeout" ); - vm.expectRevert("a paused clock can't timeout"); + vm.expectRevert(ITournament.PausedClockCannotTimeout.selector); ExternalClock.timeSinceTimeout(clock3); } } From 5fcc0341c73cfad70a963e972c808f71ec9e9d1a Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Apr 2026 13:49:13 -0300 Subject: [PATCH 008/113] refactor!: define custom errors for Match lib --- prt/contracts/src/ITournament.sol | 20 ++++++++++++++++++ prt/contracts/src/tournament/Tournament.sol | 10 ++++----- prt/contracts/src/tournament/libs/Match.sol | 22 ++++++++------------ prt/contracts/test/Match.t.sol | 23 +++++++-------------- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index 948d74703..d26de970e 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -358,6 +358,26 @@ interface ITournament { /// zero allowance is used to indicate that such a clock is not initialized. error InitializedClockCannotHaveZeroAllowance(); + /// @notice The match does not exist. + /// @dev This happens when the match ID hash is zero + /// or when the match state is not initialized. + error MatchDoesNotExist(); + + /// @notice The match is not sealed. + /// @dev This happens when the current match height is + /// either 1 (ready to be sealed) or greater (ready to be advanced). + error MatchIsNotSealed(); + + /// @notice The match cannot be sealed. + /// @dev This happens when the current match height is + /// either 0 (sealed) or greater than 1 (ready to be advanced). + error MatchCannotBeSealed(); + + /// @notice The match cannot be advanced. + /// @dev This happens when the current match height is + /// either 0 (sealed) or 1 (ready to be sealed). + error MatchCannotBeAdvanced(); + // // Functions // diff --git a/prt/contracts/src/tournament/Tournament.sol b/prt/contracts/src/tournament/Tournament.sol index d0eeefb8b..8098278ed 100644 --- a/prt/contracts/src/tournament/Tournament.sol +++ b/prt/contracts/src/tournament/Tournament.sol @@ -417,7 +417,7 @@ contract Tournament is ITournament { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireExist(); - _matchState.requireCanBeFinalized(); + _matchState.requireCanBeSealed(); { Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; @@ -457,7 +457,7 @@ contract Tournament is ITournament { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireExist(); - _matchState.requireIsFinished(); + _matchState.requireIsSealed(); ( Machine.Hash _agreeHash, @@ -538,7 +538,7 @@ contract Tournament is ITournament { } Match.State storage _matchState = matches[_matchId.hashFromId()]; - _matchState.requireCanBeFinalized(); + _matchState.requireCanBeSealed(); Time.Duration _maxDuration; { @@ -595,7 +595,7 @@ contract Tournament is ITournament { Match.State storage _matchState = matches[_matchIdHash]; _matchState.requireExist(); - _matchState.requireIsFinished(); + _matchState.requireIsSealed(); require( !_childTournament.canBeEliminated(), @@ -655,7 +655,7 @@ contract Tournament is ITournament { Match.State storage _matchState = matches[_matchIdHash]; _matchState.requireExist(); - _matchState.requireIsFinished(); + _matchState.requireIsSealed(); require( _childTournament.canBeEliminated(), diff --git a/prt/contracts/src/tournament/libs/Match.sol b/prt/contracts/src/tournament/libs/Match.sol index d2b83dc12..9f44149fb 100644 --- a/prt/contracts/src/tournament/libs/Match.sol +++ b/prt/contracts/src/tournament/libs/Match.sol @@ -47,12 +47,8 @@ library Match { 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"); + require(!idHash.isZero(), ITournament.MatchDoesNotExist()); } // @@ -171,11 +167,11 @@ library Match { return state.isInit; } - function isFinished(State memory state) internal pure returns (bool) { + function isSealed(State memory state) internal pure returns (bool) { return state.currentHeight == 0; } - function canBeFinalized(State memory state) internal pure returns (bool) { + function canBeSealed(State memory state) internal pure returns (bool) { return state.currentHeight == 1; } @@ -231,19 +227,19 @@ library Match { // Requires // function requireExist(State memory state) internal pure { - require(state.exists(), "match does not exist"); + require(state.exists(), ITournament.MatchDoesNotExist()); } - function requireIsFinished(State memory state) internal pure { - require(state.isFinished(), "match is not finished"); + function requireIsSealed(State memory state) internal pure { + require(state.isSealed(), ITournament.MatchIsNotSealed()); } - function requireCanBeFinalized(State memory state) internal pure { - require(state.canBeFinalized(), "match is not ready to be finalized"); + function requireCanBeSealed(State memory state) internal pure { + require(state.canBeSealed(), ITournament.MatchCannotBeSealed()); } function requireCanBeAdvanced(State memory state) internal pure { - require(state.canBeAdvanced(), "match can't be advanced"); + require(state.canBeAdvanced(), ITournament.MatchCannotBeAdvanced()); } function requireParentHasChildren( diff --git a/prt/contracts/test/Match.t.sol b/prt/contracts/test/Match.t.sol index 021f20436..fd1045e54 100644 --- a/prt/contracts/test/Match.t.sol +++ b/prt/contracts/test/Match.t.sol @@ -20,10 +20,6 @@ import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; library ExternalMatch { - function requireEq(Match.IdHash left, Match.IdHash right) external pure { - Match.requireEq(left, right); - } - function advanceMatch( Match.State storage state, Tree.Node leftNode, @@ -140,7 +136,7 @@ contract MatchTest is Test { assertTrue(advanceMatchStateLeft.leftNode.eq(Tree.ZERO_NODE)); assertTrue(advanceMatchStateLeft.rightNode.eq(Tree.ZERO_NODE)); - advanceMatchStateLeft.requireCanBeFinalized(); + advanceMatchStateLeft.requireCanBeSealed(); ExternalMatch.sealMatch( advanceMatchStateLeft, args, @@ -151,7 +147,7 @@ contract MatchTest is Test { new bytes32[](0) ); - advanceMatchStateLeft.requireIsFinished(); + advanceMatchStateLeft.requireIsSealed(); (Machine.Hash agreeHash, uint256 agreeCycle,,) = advanceMatchStateLeft.getDivergence(args); @@ -202,7 +198,7 @@ contract MatchTest is Test { proof[0] = Tree.Node.unwrap(ONE_NODE); proof[1] = Tree.Node.unwrap(Tree.ZERO_NODE); - advanceMatchStateRight.requireCanBeFinalized(); + advanceMatchStateRight.requireCanBeSealed(); ExternalMatch.sealMatch( advanceMatchStateRight, args, @@ -213,7 +209,7 @@ contract MatchTest is Test { proof ); - advanceMatchStateRight.requireIsFinished(); + advanceMatchStateRight.requireIsSealed(); (Machine.Hash agreeHash, uint256 agreeCycle,,) = advanceMatchStateRight.getDivergence(args); @@ -284,7 +280,7 @@ contract MatchTest is Test { proof[1] = Tree.Node.unwrap(Tree.ZERO_NODE); proof[2] = Tree.Node.unwrap(Tree.ZERO_NODE); - advanceMatchStateRight.requireCanBeFinalized(); + advanceMatchStateRight.requireCanBeSealed(); ExternalMatch.sealMatch( advanceMatchStateRight, args, @@ -295,7 +291,7 @@ contract MatchTest is Test { proof ); - advanceMatchStateRight.requireIsFinished(); + advanceMatchStateRight.requireIsSealed(); (Machine.Hash agreeHash, uint256 agreeCycle,,) = advanceMatchStateRight.getDivergence(args); @@ -375,16 +371,11 @@ contract MatchTest is Test { ); } - function testEqual() public { + function testEqual() public view { assertTrue(leftDivergenceMatchIdHash.eq(leftDivergenceMatchIdHash)); assertTrue(rightDivergenceMatchIdHash.eq(rightDivergenceMatchIdHash)); assertTrue(!leftDivergenceMatchIdHash.eq(rightDivergenceMatchIdHash)); assertTrue(!rightDivergenceMatchIdHash.eq(leftDivergenceMatchIdHash)); - - vm.expectRevert("matches are not equal"); - ExternalMatch.requireEq( - leftDivergenceMatchIdHash, rightDivergenceMatchIdHash - ); } function testIdHash() public pure { From 378feb952ca9499d4cb519b3de5cdc43f32c812a Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Thu, 2 Apr 2026 06:41:27 -0300 Subject: [PATCH 009/113] refactor!: define custom errors for Tree lib --- prt/contracts/src/ITournament.sol | 13 +++++++++++++ prt/contracts/src/types/Tree.sol | 8 ++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/prt/contracts/src/ITournament.sol b/prt/contracts/src/ITournament.sol index d26de970e..3f894e56c 100644 --- a/prt/contracts/src/ITournament.sol +++ b/prt/contracts/src/ITournament.sol @@ -378,6 +378,19 @@ interface ITournament { /// either 0 (sealed) or 1 (ready to be sealed). error MatchCannotBeAdvanced(); + /// @notice The parent of the provided children nodes + /// is different from the expected parent node. + /// @param expectedParent The expected parent node + /// @param leftChild The left child node + /// @param rightChild The right child node + error InvalidChildrenNodes( + Tree.Node expectedParent, Tree.Node leftChild, Tree.Node rightChild + ); + + /// @notice The node does not exist. + /// @dev This happens when the node is zero. + error NodeDoesNotExist(); + // // Functions // diff --git a/prt/contracts/src/types/Tree.sol b/prt/contracts/src/types/Tree.sol index 9e7f8e63d..2e82f7dea 100644 --- a/prt/contracts/src/types/Tree.sol +++ b/prt/contracts/src/types/Tree.sol @@ -7,6 +7,7 @@ import { Hashes } from "@openzeppelin-contracts-5.5.0/utils/cryptography/Hashes.sol"; +import {ITournament} from "prt-contracts/ITournament.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; library Tree { @@ -38,7 +39,10 @@ library Tree { } function requireChildren(Node parent, Node left, Node right) internal pure { - require(parent.verify(left, right), "child nodes don't match parent"); + require( + parent.verify(left, right), + ITournament.InvalidChildrenNodes(parent, left, right) + ); } function isZero(Node node) internal pure returns (bool) { @@ -47,7 +51,7 @@ library Tree { } function requireExist(Node node) internal pure { - require(!node.isZero(), "tree node doesn't exist"); + require(!node.isZero(), ITournament.NodeDoesNotExist()); } function toMachineHash(Node node) internal pure returns (Machine.Hash) { From 9f6a3c5007b776eba6f0743b8205276036bbee0a Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Thu, 2 Apr 2026 23:47:40 -0300 Subject: [PATCH 010/113] feat!: bump `rollups-contracts` from 2.2.0 to 3.0.0-alpha.3 --- cartesi-rollups/contracts/cannonfile.toml | 21 - cartesi-rollups/contracts/foundry.toml | 9 +- .../contracts/script/Deployment.s.sol | 13 +- cartesi-rollups/contracts/script/deploy.sh | 2 +- cartesi-rollups/contracts/soldeer.lock | 8 +- .../contracts/src/CannonDependencies.sol | 9 - .../contracts/src/DaveAppFactory.sol | 40 +- .../contracts/src/DaveConsensus.sol | 61 +- .../contracts/src/IDaveAppFactory.sol | 9 +- .../contracts/src/IDaveConsensus.sol | 10 +- cartesi-rollups/contracts/src/Math.sol | 67 -- cartesi-rollups/contracts/src/Merkle.sol | 152 ---- .../contracts/src/MerkleConstants.sol | 11 - .../contracts/src/PristineMerkleTree.sol | 24 - .../contracts/src/TestFungibleToken.sol | 16 - .../contracts/src/TestMultiToken.sol | 18 - .../contracts/src/TestNonFungibleToken.sol | 16 - .../contracts/test/DaveAppFactory.t.sol | 817 +++++++++++++++--- .../contracts/test/DaveConsensus.t.sol | 642 -------------- cartesi-rollups/contracts/test/Math.t.sol | 84 -- cartesi-rollups/contracts/test/Merkle.t.sol | 379 -------- .../node/blockchain-reader/src/lib.rs | 23 +- .../node/blockchain-reader/src/test_utils.rs | 20 +- prt/tests/rollups/dave/reader.lua | 6 +- prt/tests/rollups/dave/sender.lua | 6 +- prt/tests/rollups/test_cases/big_input.lua | 2 +- prt/tests/rollups/test_cases/gc_match.lua | 28 +- .../rollups/test_cases/gc_tournament.lua | 27 +- prt/tests/rollups/test_cases/simple.lua | 2 +- prt/tests/rollups/test_cases/stf_all.lua | 2 +- prt/tests/rollups/test_env.lua | 2 +- 31 files changed, 874 insertions(+), 1652 deletions(-) delete mode 100644 cartesi-rollups/contracts/src/CannonDependencies.sol delete mode 100644 cartesi-rollups/contracts/src/Math.sol delete mode 100644 cartesi-rollups/contracts/src/Merkle.sol delete mode 100644 cartesi-rollups/contracts/src/MerkleConstants.sol delete mode 100644 cartesi-rollups/contracts/src/PristineMerkleTree.sol delete mode 100644 cartesi-rollups/contracts/src/TestFungibleToken.sol delete mode 100644 cartesi-rollups/contracts/src/TestMultiToken.sol delete mode 100644 cartesi-rollups/contracts/src/TestNonFungibleToken.sol delete mode 100644 cartesi-rollups/contracts/test/DaveConsensus.t.sol delete mode 100644 cartesi-rollups/contracts/test/Math.t.sol delete mode 100644 cartesi-rollups/contracts/test/Merkle.t.sol diff --git a/cartesi-rollups/contracts/cannonfile.toml b/cartesi-rollups/contracts/cannonfile.toml index 7cc415b04..7fcdf21f0 100644 --- a/cartesi-rollups/contracts/cannonfile.toml +++ b/cartesi-rollups/contracts/cannonfile.toml @@ -18,24 +18,3 @@ args = [ create2 = true salt = "<%= zeroHash %>" ifExists = "continue" - -[deploy.TestFungibleToken] -artifact = "TestFungibleToken" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" -chains = [13370] - -[deploy.TestNonFungibleToken] -artifact = "TestNonFungibleToken" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" -chains = [13370] - -[deploy.TestMultiToken] -artifact = "TestMultiToken" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" -chains = [13370] diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index 3a149ca71..5a3ad0cba 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -8,9 +8,10 @@ via_ir = true allow_paths = ["../../prt/contracts", "../../machine/step"] remappings = [ - "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-2.2.0/dependencies/@openzeppelin-contracts-5.2.0/", + "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/dependencies/@openzeppelin-contracts-5.2.0/", "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/", - "cartesi-rollups-contracts-2.2.0/=dependencies/cartesi-rollups-contracts-2.2.0/", + "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/dependencies/cartesi-machine-solidity-step-0.13.0/", + "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/", "forge-std-1.9.6/=dependencies/forge-std-1.9.6/", "prt-contracts/=../../prt/contracts/src/", "step/=../../machine/step/", @@ -20,7 +21,7 @@ solc_version = "0.8.30" evm_version = "prague" fs_permissions = [ { access = "read-write", path = "deployments" }, - { access = "read", path = "dependencies/cartesi-rollups-contracts-2.2.0/deployments" }, + { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/deployments" }, { access = "read", path = "../../prt/contracts/deployments" }, ] @@ -37,4 +38,4 @@ exclude_lints = ["incorrect-shift"] [dependencies] "@openzeppelin-contracts" = "5.5.0" forge-std = "1.9.6" -cartesi-rollups-contracts = "2.2.0" +cartesi-rollups-contracts = "3.0.0-alpha.3" diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index 376623487..5974bfc1f 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -6,14 +6,11 @@ pragma solidity ^0.8.8; import {BaseDeploymentScript} from "prt-contracts/../script/BaseDeploymentScript.sol"; import {DaveAppFactory} from "src/DaveAppFactory.sol"; -import {TestFungibleToken} from "src/TestFungibleToken.sol"; -import {TestMultiToken} from "src/TestMultiToken.sol"; -import {TestNonFungibleToken} from "src/TestNonFungibleToken.sol"; contract DeploymentScript is BaseDeploymentScript { function run() external { _importDeployments("../../prt/contracts"); - _importDeployments("dependencies/cartesi-rollups-contracts-2.2.0"); + _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.3"); address inputBox = _loadDeployment(".", "InputBox"); address appFactory = _loadDeployment(".", "ApplicationFactory"); @@ -26,14 +23,6 @@ contract DeploymentScript is BaseDeploymentScript { _create2(type(DaveAppFactory).creationCode, abi.encode(inputBox, appFactory, tournamentFactory)) ); - if (block.chainid == 31337) { - /// forgefmt: disable-start - _storeDeployment(type(TestFungibleToken).name, _create2(type(TestFungibleToken).creationCode, abi.encode())); - _storeDeployment(type(TestNonFungibleToken).name, _create2(type(TestNonFungibleToken).creationCode, abi.encode())); - _storeDeployment(type(TestMultiToken).name, _create2(type(TestMultiToken).creationCode, abi.encode())); - /// forgefmt: disable-end - } - vmSafe.stopBroadcast(); } } diff --git a/cartesi-rollups/contracts/script/deploy.sh b/cartesi-rollups/contracts/script/deploy.sh index d54ccc0df..82d972be6 100755 --- a/cartesi-rollups/contracts/script/deploy.sh +++ b/cartesi-rollups/contracts/script/deploy.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." roots=( '../../prt/contracts' - 'dependencies/cartesi-rollups-contracts-2.2.0' + 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.3' '.' ) diff --git a/cartesi-rollups/contracts/soldeer.lock b/cartesi-rollups/contracts/soldeer.lock index 276d1bc9a..8058eeab8 100644 --- a/cartesi-rollups/contracts/soldeer.lock +++ b/cartesi-rollups/contracts/soldeer.lock @@ -7,10 +7,10 @@ integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" [[dependencies]] name = "cartesi-rollups-contracts" -version = "2.2.0" -url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/2_2_0_06-02-2026_19:12:57_rollups-contracts.zip" -checksum = "8ec1e639104f544cfb198629cb258c54cfe86382fbc8402ca3e831406887fa08" -integrity = "8af3eb0d2bdfe558ae0260562c617ff5aa79b687f584bc6687fdaedd93da3a02" +version = "3.0.0-alpha.3" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_3_27-03-2026_20:38:21_rollups-contracts.zip" +checksum = "3d2ef3f5647f80d7549eed84b589fb612901c88c6f5f26cdf9b4a44d358b8b9a" +integrity = "8bb84e8623fc67af07bb7c096d49e0cc9cc1702a1cf600b7b1b8167ee1af1768" [[dependencies]] name = "forge-std" diff --git a/cartesi-rollups/contracts/src/CannonDependencies.sol b/cartesi-rollups/contracts/src/CannonDependencies.sol deleted file mode 100644 index 1ace6a5d0..000000000 --- a/cartesi-rollups/contracts/src/CannonDependencies.sol +++ /dev/null @@ -1,9 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -// List of contracts used in Cannonfiles - -/// forge-lint: disable-next-line(unused-import) -import {ApplicationFactory} from "cartesi-rollups-contracts-2.2.0/src/dapp/ApplicationFactory.sol"; diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index aa36333c9..c051735d5 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -5,13 +5,14 @@ pragma solidity ^0.8.8; import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; -import {DataAvailability} from "cartesi-rollups-contracts-2.2.0/src/common/DataAvailability.sol"; +import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; +import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; import { IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-2.2.0/src/consensus/IOutputsMerkleRootValidator.sol"; -import {IApplication} from "cartesi-rollups-contracts-2.2.0/src/dapp/IApplication.sol"; -import {IApplicationFactory} from "cartesi-rollups-contracts-2.2.0/src/dapp/IApplicationFactory.sol"; -import {IInputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/IInputBox.sol"; +} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; +import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol"; +import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; @@ -33,25 +34,25 @@ contract DaveAppFactory is IDaveAppFactory { TOURNAMENT_FACTORY = tournamentFactory; } - function newDaveApp(bytes32 templateHash, bytes32 salt) + function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external override returns (IApplication appContract, IDaveConsensus daveConsensus) { - appContract = _newApplication(templateHash, salt); + appContract = _newApplication(templateHash, withdrawalConfig, salt); daveConsensus = _newDaveConsensus(address(appContract), templateHash, salt); appContract.migrateToOutputsMerkleRootValidator(daveConsensus); appContract.renounceOwnership(); emit DaveAppCreated(appContract, daveConsensus); } - function calculateDaveAppAddress(bytes32 templateHash, bytes32 salt) + function calculateDaveAppAddress(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external view override returns (address appContractAddress, address daveConsensusAddress) { - appContractAddress = _calculateApplicationAddress(templateHash, salt); + appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt); daveConsensusAddress = _calculateDaveConsensusAddress(appContractAddress, templateHash, salt); } @@ -63,9 +64,15 @@ contract DaveAppFactory is IDaveAppFactory { /// @notice Instantiate a new application contract owned by the current contract, /// with no outputs Merkle root validator (the zero address), and with the input box /// as the only data availability source. - function _newApplication(bytes32 templateHash, bytes32 salt) internal returns (IApplication) { + function _newApplication(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) + internal + returns (IApplication) + { bytes memory dataAvailability = _encodeInputBoxDataAvailability(); - return APP_FACTORY.newApplication(NO_VALIDATOR, address(this), templateHash, dataAvailability, salt); + return + APP_FACTORY.newApplication( + NO_VALIDATOR, address(this), templateHash, dataAvailability, withdrawalConfig, salt + ); } /// @notice Instantiate a new `DaveConsensus` contract. @@ -78,10 +85,15 @@ contract DaveAppFactory is IDaveAppFactory { } /// @notice Calculates the address of an application contract. - function _calculateApplicationAddress(bytes32 templateHash, bytes32 salt) internal view returns (address) { + function _calculateApplicationAddress( + bytes32 templateHash, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) internal view returns (address) { bytes memory dataAvailability = _encodeInputBoxDataAvailability(); - return - APP_FACTORY.calculateApplicationAddress(NO_VALIDATOR, address(this), templateHash, dataAvailability, salt); + return APP_FACTORY.calculateApplicationAddress( + NO_VALIDATOR, address(this), templateHash, dataAvailability, withdrawalConfig, salt + ); } /// @notice Calculates the address of a `DaveConsensus` contract. diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index f9b06182e..eabf87f7c 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -8,9 +8,12 @@ import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165 import { IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-2.2.0/src/consensus/IOutputsMerkleRootValidator.sol"; -import {IInputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/IInputBox.sol"; -import {LibMerkle32} from "cartesi-rollups-contracts-2.2.0/src/library/LibMerkle32.sol"; +} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {ApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/ApplicationChecker.sol"; +import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; +import {LibBinaryMerkleTree} from "cartesi-rollups-contracts-3.0.0/src/library/LibBinaryMerkleTree.sol"; +import {LibKeccak256} from "cartesi-rollups-contracts-3.0.0/src/library/LibKeccak256.sol"; +import {LibMath} from "cartesi-rollups-contracts-3.0.0/src/library/LibMath.sol"; import {IDataProvider} from "prt-contracts/IDataProvider.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; @@ -23,7 +26,6 @@ import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; import {Memory} from "step/src/Memory.sol"; import {IDaveConsensus} from "./IDaveConsensus.sol"; -import {Merkle} from "./Merkle.sol"; /// @notice Consensus contract with Dave tournaments. /// @@ -47,9 +49,10 @@ import {Merkle} from "./Merkle.sol"; /// the accumlating epoch will be sealed, and a new /// accumulating epoch will be created. /// -contract DaveConsensus is IDaveConsensus, ERC165 { - using Merkle for bytes; - using LibMerkle32 for bytes32[]; +contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { + using LibMath for uint256; + using LibBinaryMerkleTree for bytes; + using LibBinaryMerkleTree for bytes32[]; /// @notice The input box contract IInputBox immutable _INPUT_BOX; @@ -78,6 +81,9 @@ contract DaveConsensus is IDaveConsensus, ERC165 { /// @notice Settled output trees' merkle root hash mapping(bytes32 => bool) _outputsMerkleRoots; + /// @notice Last-finalized machine state hash + Machine.Hash _lastFinalizedMachineStateHash; + constructor( IInputBox inputBox, address appContract, @@ -108,7 +114,11 @@ contract DaveConsensus is IDaveConsensus, ERC165 { epochNumber = _epochNumber; } - function settle(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) external override { + function settle(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) + external + override + notForeclosed(_APP_CONTRACT) + { // Check tournament settlement require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber)); @@ -121,11 +131,12 @@ contract DaveConsensus is IDaveConsensus, ERC165 { // Check outputs Merkle root _validateOutputTree(finalMachineStateHash, outputsMerkleRoot, proof); - // Seal current accumulating epoch, save settled output tree + // Seal current accumulating epoch, save settled output tree and machine state hash _epochNumber++; _inputIndexLowerBound = _inputIndexUpperBound; _inputIndexUpperBound = _INPUT_BOX.getNumberOfInputs(_APP_CONTRACT); _outputsMerkleRoots[outputsMerkleRoot] = true; + _lastFinalizedMachineStateHash = finalMachineStateHash; // Start new tournament _tournament = _TOURNAMENT_FACTORY.instantiate(finalMachineStateHash, this); @@ -184,25 +195,35 @@ contract DaveConsensus is IDaveConsensus, ERC165 { return bytes32(0); } - /// forge-lint: disable-next-line(asm-keccak256) - bytes32 calculatedInputHash = keccak256(input); + bytes32 calculatedInputHash = LibKeccak256.hashBytes(input); bytes32 realInputHash = _INPUT_BOX.getInputHash(_APP_CONTRACT, inputIndex); require(calculatedInputHash == realInputHash, InputHashMismatch(calculatedInputHash, realInputHash)); - uint256 log2SizeOfDrive = input.getMinLog2SizeOfDrive(); - return input.getMerkleRootFromBytes(log2SizeOfDrive); + uint256 log2DataBlockSize = Memory.LOG2_LEAF; + uint256 log2DriveSize = input.length.ceilLog2().max(log2DataBlockSize); + return input.merkleRoot(log2DriveSize, log2DataBlockSize, LibKeccak256.hashBlock, LibKeccak256.hashPair); } function isOutputsMerkleRootValid(address appContract, bytes32 outputsMerkleRoot) public view override + onlyValidAppContract(appContract) returns (bool) { - require(_APP_CONTRACT == appContract, ApplicationMismatch(_APP_CONTRACT, appContract)); return _outputsMerkleRoots[outputsMerkleRoot]; } + function getLastFinalizedMachineMerkleRoot(address appContract) + external + view + override + onlyValidAppContract(appContract) + returns (bytes32) + { + return Machine.Hash.unwrap(_lastFinalizedMachineStateHash); + } + function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC165) returns (bool) { return interfaceId == type(IDataProvider).interfaceId || interfaceId == type(IOutputsMerkleRootValidator).interfaceId || super.supportsInterface(interfaceId); @@ -222,9 +243,19 @@ contract DaveConsensus is IDaveConsensus, ERC165 { require(proof.length == Memory.LOG2_MAX_SIZE, InvalidOutputsMerkleRootProofSize(proof.length)); bytes32 allegedStateHash = proof.merkleRootAfterReplacement( EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, - keccak256(abi.encode(outputsMerkleRoot)) + keccak256(abi.encode(outputsMerkleRoot)), + LibKeccak256.hashPair ); require(machineStateHash == allegedStateHash, InvalidOutputsMerkleRootProof(finalMachineStateHash)); } + + modifier onlyValidAppContract(address appContract) { + _ensureAppContractIsValid(appContract); + _; + } + + function _ensureAppContractIsValid(address appContract) internal view { + require(_APP_CONTRACT == appContract, ApplicationMismatch(_APP_CONTRACT, appContract)); + } } diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index 9a4ded921..0c6a626c6 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.8; -import {IApplication} from "cartesi-rollups-contracts-2.2.0/src/dapp/IApplication.sol"; +import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; +import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; import {IDaveConsensus} from "./IDaveConsensus.sol"; @@ -18,19 +19,21 @@ interface IDaveAppFactory { /// @notice Deploy a new Dave-App pair deterministically. /// @param templateHash The application template hash + /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContract The application contract /// @return daveConsensus The Dave consensus contract - function newDaveApp(bytes32 templateHash, bytes32 salt) + function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external returns (IApplication appContract, IDaveConsensus daveConsensus); /// @notice Calculate the address of a Dave-App pair. /// @param templateHash The application template hash + /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContractAddress The application contract address /// @return daveConsensusAddress The Dave consensus contract address - function calculateDaveAppAddress(bytes32 templateHash, bytes32 salt) + function calculateDaveAppAddress(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external view returns (address appContractAddress, address daveConsensusAddress); diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 760e61994..092228b20 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -3,12 +3,14 @@ pragma solidity ^0.8.8; +import {BinaryMerkleTreeErrors} from "cartesi-rollups-contracts-3.0.0/src/common/BinaryMerkleTreeErrors.sol"; import { IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-2.2.0/src/consensus/IOutputsMerkleRootValidator.sol"; -import {IInputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/IInputBox.sol"; -import {IDataProvider} from "prt-contracts/IDataProvider.sol"; +} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; +import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; +import {IDataProvider} from "prt-contracts/IDataProvider.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; @@ -37,7 +39,7 @@ import {Tree} from "prt-contracts/types/Tree.sol"; /// the accumlating epoch will be sealed, and a new /// accumulating epoch will be created. /// -interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator { +interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplicationChecker, BinaryMerkleTreeErrors { /// @notice Consensus contract was created /// @param inputBox the input box contract /// @param appContract the application contract diff --git a/cartesi-rollups/contracts/src/Math.sol b/cartesi-rollups/contracts/src/Math.sol deleted file mode 100644 index 015249b02..000000000 --- a/cartesi-rollups/contracts/src/Math.sol +++ /dev/null @@ -1,67 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -/// @author Felipe Argento -library Math { - /// @notice count trailing zeros - /// @param x number you want the ctz of - /// @dev this a binary search implementation - function ctz(uint256 x) internal pure returns (uint256) { - if (x == 0) return 256; - else return 256 - clz(~x & (x - 1)); - } - - /// @notice count leading zeros - /// @param x number you want the clz of - /// @dev this a binary search implementation - function clz(uint256 x) internal pure returns (uint256 n) { - if (x == 0) return 256; - - if (x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000 == 0) { - n = n + 128; - x = x << 128; - } - if (x & 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000 == 0) { - n = n + 64; - x = x << 64; - } - if (x & 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 32; - x = x << 32; - } - if (x & 0xFFFF000000000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 16; - x = x << 16; - } - if (x & 0xFF00000000000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 8; - x = x << 8; - } - if (x & 0xF000000000000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 4; - x = x << 4; - } - if (x & 0xC000000000000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 2; - x = x << 2; - } - if (x & 0x8000000000000000000000000000000000000000000000000000000000000000 == 0) { - n = n + 1; - } - } - - /// @notice the smallest y for which x <= 2^y - /// @param x number you want the log2clp of - /// @dev this a binary search implementation - function log2clp(uint256 x) internal pure returns (uint256) { - if (x == 0) return 0; - else return 256 - clz(x - 1); - } - - /// @notice the largest of two numbers - function max(uint256 x, uint256 y) internal pure returns (uint256) { - return (x > y) ? x : y; - } -} diff --git a/cartesi-rollups/contracts/src/Merkle.sol b/cartesi-rollups/contracts/src/Merkle.sol deleted file mode 100644 index af161b0c1..000000000 --- a/cartesi-rollups/contracts/src/Merkle.sol +++ /dev/null @@ -1,152 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -import {Math} from "./Math.sol"; -import {MerkleConstants} from "./MerkleConstants.sol"; -import {PristineMerkleTree} from "./PristineMerkleTree.sol"; - -library Merkle { - using Math for uint256; - - /// @notice Compute the hash of the concatenation of two 32-byte values. - /// @param a The first value - /// @param b The second value - /// @return c The result of `keccak256(abi.encodePacked(a, b))` - /// @dev Uses assembly for better performance. - function join(bytes32 a, bytes32 b) internal pure returns (bytes32 c) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, a) - mstore(0x20, b) - c := keccak256(0x00, 0x40) - } - } - - /// @notice Get the Merkle root hash of a drive with a replacement. - /// @param position position of replacement in drive - /// @param log2SizeOfReplacement log2 of the size of the replacement - /// @param log2SizeOfDrive log2 of the size of the drive - /// @param replacement the hash of the replacement - /// @param siblings of replacement in bottom-up order - function getRootAfterReplacementInDrive( - uint256 position, - uint256 log2SizeOfReplacement, - uint256 log2SizeOfDrive, - bytes32 replacement, - bytes32[] calldata siblings - ) internal pure returns (bytes32) { - require(log2SizeOfReplacement >= MerkleConstants.LOG2_LEAF_SIZE, "Replacement smaller than leaf"); - require(log2SizeOfDrive <= MerkleConstants.LOG2_MEMORY_SIZE, "Drive larger than memory"); - require(log2SizeOfDrive >= log2SizeOfReplacement, "Replacement larger than drive"); - - uint256 sizeOfReplacement = 1 << log2SizeOfReplacement; - - // check if `position` is a multiple of `sizeOfReplacement` - require(((sizeOfReplacement - 1) & position) == 0, "Position is not aligned"); - - require(siblings.length == log2SizeOfDrive - log2SizeOfReplacement, "Proof length does not match"); - - for (uint256 i; i < siblings.length; ++i) { - if ((position & (sizeOfReplacement << i)) == 0) { - replacement = join(replacement, siblings[i]); - } else { - replacement = join(siblings[i], replacement); - } - } - - return replacement; - } - - /// @notice Get the log2 of the smallest drive that first the provided data. - /// @param data the byte array - /// @dev If data is smaller than the drive, it is padded with zeros. - /// @dev The smallest tree covers at least one leaf. - /// @dev See `MerkleConstants` for leaf size. - function getMinLog2SizeOfDrive(bytes calldata data) internal pure returns (uint256) { - return data.length.log2clp().max(MerkleConstants.LOG2_LEAF_SIZE); - } - - /// @notice Get the Merkle root of a byte array. - /// @param data the byte array - /// @param log2SizeOfDrive log2 of size of the drive - /// @dev If data is smaller than the drive, it is padded with zeros. - /// @dev See `MerkleConstants` for leaf size. - function getMerkleRootFromBytes(bytes calldata data, uint256 log2SizeOfDrive) internal pure returns (bytes32) { - require(log2SizeOfDrive >= MerkleConstants.LOG2_LEAF_SIZE, "Drive smaller than leaf"); - require(log2SizeOfDrive <= MerkleConstants.LOG2_MEMORY_SIZE, "Drive larger than memory"); - - uint256 log2NumOfLeavesInDrive = log2SizeOfDrive - MerkleConstants.LOG2_LEAF_SIZE; - - // if data is empty, then return node from pristine Merkle tree - if (data.length == 0) { - return PristineMerkleTree.getNodeAtHeight(log2NumOfLeavesInDrive); - } - - uint256 numOfLeavesInDrive = 1 << log2NumOfLeavesInDrive; - - require(data.length <= (numOfLeavesInDrive << MerkleConstants.LOG2_LEAF_SIZE), "Data larger than drive"); - - // Note: This is a very generous stack depth. - bytes32[] memory stack = new bytes32[](2 + log2NumOfLeavesInDrive); - - uint256 numOfHashes; // total number of leaves covered up until now - uint256 stackLength; // total length of stack - uint256 numOfJoins; // number of hashes of the same level on stack - uint256 topStackLevel; // level of hash on top of the stack - - while (numOfHashes < numOfLeavesInDrive) { - if ((numOfHashes << MerkleConstants.LOG2_LEAF_SIZE) < data.length) { - // we still have leaves to hash - stack[stackLength] = getHashOfLeafAtIndex(data, numOfHashes); - numOfHashes++; - - numOfJoins = numOfHashes; - } else { - // since padding happens in getHashOfLeafAtIndex function - // we only need to complete the stack with pre-computed - // hash(0), hash(hash(0),hash(0)) and so on - topStackLevel = numOfHashes.ctz(); - - stack[stackLength] = PristineMerkleTree.getNodeAtHeight(topStackLevel); - - //Empty Tree Hash summarizes many hashes - numOfHashes = numOfHashes + (1 << topStackLevel); - numOfJoins = numOfHashes >> topStackLevel; - } - - stackLength++; - - // while there are joins, hash top of stack together - while (numOfJoins & 1 == 0) { - bytes32 h2 = stack[stackLength - 1]; - bytes32 h1 = stack[stackLength - 2]; - - stack[stackLength - 2] = join(h1, h2); - stackLength = stackLength - 1; // remove hashes from stack - - numOfJoins = numOfJoins >> 1; - } - } - - require(stackLength == 1, "stack error"); - - return stack[0]; - } - - /// @notice Get the hash of a leaf from a byte array by its index. - /// @param data the byte array - /// @param leafIndex the leaf index - /// @dev The data is assumed to be followed by an infinite sequence of zeroes. - /// @dev See `MerkleConstants` for leaf size. - function getHashOfLeafAtIndex(bytes calldata data, uint256 leafIndex) internal pure returns (bytes32) { - uint256 start = leafIndex << MerkleConstants.LOG2_LEAF_SIZE; - if (start < data.length) { - /// forge-lint: disable-next-line(asm-keccak256) - return keccak256(abi.encode(bytes32(data[start:]))); - } else { - return PristineMerkleTree.getNodeAtHeight(0); - } - } -} diff --git a/cartesi-rollups/contracts/src/MerkleConstants.sol b/cartesi-rollups/contracts/src/MerkleConstants.sol deleted file mode 100644 index 9c7ed73ae..000000000 --- a/cartesi-rollups/contracts/src/MerkleConstants.sol +++ /dev/null @@ -1,11 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -library MerkleConstants { - uint256 constant HASH_SIZE = 32; - uint256 constant LOG2_MEMORY_SIZE = 64; - uint256 constant LOG2_LEAF_SIZE = 5; - uint256 constant TREE_HEIGHT = LOG2_MEMORY_SIZE - LOG2_LEAF_SIZE; -} diff --git a/cartesi-rollups/contracts/src/PristineMerkleTree.sol b/cartesi-rollups/contracts/src/PristineMerkleTree.sol deleted file mode 100644 index 0e160d910..000000000 --- a/cartesi-rollups/contracts/src/PristineMerkleTree.sol +++ /dev/null @@ -1,24 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -import {MerkleConstants} from "./MerkleConstants.sol"; - -library PristineMerkleTree { - /// @notice The nodes of the pristine Merkle tree in bottom-up order, - /// tightly packed into a single byte array - bytes constant NODES = - hex"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563633dc4d7da7256660a892f8f1604a44b5432649cc8ec5cb3ced4c4e6ac94dd1d890740a8eb06ce9be422cb8da5cdafc2b58c0a5e24036c578de2a433c828ff7d3b8ec09e026fdc305365dfc94e189a81b38c7597b3d941c279f042e8206e0bd8ecd50eee38e386bd62be9bedb990706951b65fe053bd9d8a521af753d139e2dadefff6d330bb5403f63b14f33b578274160de3a50df4efecf0e0db73bcdd3da5617bdd11f7c0a11f49db22f629387a12da7596f9d1704d7465177c63d88ec7d7292c23a9aa1d8bea7e2435e555a4a60e379a5a35f3f452bae60121073fb6eeade1cea92ed99acdcb045a6726b2f87107e8a61620a232cf4d7d5b5766b3952e107ad66c0a68c72cb89e4fb4303841966e4062a76ab97451e3b9fb526a5ceb7f82e026cc5a4aed3c22a58cbd3d2ac754c9352c5436f638042dca99034e836365163d04cffd8b46a874edf5cfae63077de85f849a660426697b06a829c70dd1409cad676aa337a485e4728a0b240d92b3ef7b3c372d06d189322bfd5f61f1e7203ea2fca4a49658f9fab7aa63289c91b7c7b6c832a6d0e69334ff5b0a3483d09dab4ebfd9cd7bca2505f7bef59cc1c12ecc708fff26ae4af19abe852afe9e20c8622def10d13dd169f550f578bda343d9717a138562e0093b380a1120789d53cf10776a31db34a1a0a7caaf862cffdfff1789297ffadc380bd3d39281d340abd3ade2e7610b87a5fdf3a72ebe271287d923ab990eefac64b6e59d79f8b7e08c46e3504364a5c6858bf98fff714ab5be9de19ed31a976860efbd0e772a2efe23e2e04f05f4acb83f5b65168d9fef89d56d4d77b8944015e6b1eed81b0238e2d0dba344a6d974c75b07423e1d6d33f481916fdd45830aea11b6347e700cd8b9f0767cedf260291f734ddac396a956127dde4c34c0cfb8d8052f88ac139658ccf2d5076075c657a105351e7f0fce53bc320113324a522e8fd52dc878c762551e01a46e6ca6a3f763a9395f7da16014725ca7ee17e4815c0ff8119bf33f273dee11833b1c25ef10ffeb3c7d08aa707d17286e0b0d3cbcb50f1bd3b6523b63ba3b52dd0ffffc43bd08273ccf135fd3cacbeef055418e09eb728d727c4d5d5c556cdea7e3c5ab8111456b1f28f3c7a0a604b4553ce905cb019c463ee159137af83c350b220ff273fcbf4ae0f2bd88d6cf319ff4004f8d7dca70d4ced4e74d2c74139739e67fa06ba11241ddd5efdc65d4e39c9f6991b74fd4b81b62230808216c876f827c7e275adf313a996c7e2950cac67caba02a5ff925ebf9906b58949f3e77aec5b98f6162fa308d2b3a15dc33cffac85f13ab349173121645aedf00f471663108be78ccaaab73373552f207a63599de54d7d8d0c1805f86ce7da15818d09f4cff62cf277fb80a82478460e8988570b718f1e083ceb76f7e271a1a1497e5975f53ae4bf6ffa2bc131204513289738567a68fa9f4827dac7fd3b3d1f2e94777d57f36b49d61e8c2c894e12486176ab8f4d7069d6692fa6495541567872e7ecbddb726202b1014739f29b1d905d630ddeb8560a32bf23e666c8a1523a4a600227fef7c9e1d1ce5cdca9cdf40fa5786548b58eb19ddfd32395b4582983919099dbd153113784d01e2fae904de62c6fbf9776979ca7a2777ae2632ee278d19aca30f890e68c477f83a13a000ad2b5b3e50375b7c3ae782d987ba4b5a65376bbb97469fb371e37864edf08740d592362cd24d0db067bf14cd3b97bd2a68e782adffb4365588d0cc35e8abe7d6524569be8aa1a48bb23362326fdfefe961348bc96091c94c8545f2c1afdace5d87f06ce1d44bc0a2691aea4414d0ad640be0d9879c8374d92360c4480d69879eadd7ae508409f2f0ba83a2b0fe0da57b40008c064d2c397d31763a8e1dedb15ad0c8b88d437fef835aeb958292644810663dc1756550f22bfa4c9b1172ab5afebd159573e19e25169975242e10a2ed38dfc805232fb50a643528594d65e9525233b39587174bcf0bba562d95b50b429914c7e0e8a9ae58f91765922797ba721e8937c7470a26d5c2f141f3d4106447dab171fced7d652a69f0573e660ba01cff272a1c6f7927d73c94c85d380a84399867626d6429d48bae31682c19f715cd317da1a553ce384de6902fe26032fecc7de03e40502af05dced50c6062bfcd7bbd0dbd68d1ddeeae095485720a1ce334e9e5e5153066f42260e0bcfbd03fa4a619a5bc3cae15404f39b4b7bf5ddd5161ea642d13d23baef545acb98b6fb1a5fdae0a5718d0b812e1de1412e44d875146acc1a7f7c3916c003abb1ff291eb2e614a661d6730b502bae2bde112958cee698478845a1bb3788a6b9a91f4c19545d6cd9720d4736591fe70edcf3e33cb2e56f21fdc1244f3d2ef6dbdf82c7d252d69c64bd6fa8ee66ad9d30241016981e4580e3c2edc4804b7523663bc3b9e8340ab48a66828d7d9d7d5be2f5f280665196d25a39c4d01345cb95ea962e4bd7d1f316f845784e115ff103530d85f9dfacd638ad45fbe938ba85c40b3f100a0f506f6a6c86c10f107ab4811ddc76f72516fcdfa8a0c9bd71ff912abe7e8dbc3898515970ca6fce19039ca22ae2638e44b24641fd61048b94dc2be2531d0f66ab43019856780cb246cfd37b0c190d17111d5c016f19ba715ee622dc5"; - - /// @notice Gets a node from a certain height of the pristine Merkle tree - /// @param height the node height (0 = leaf, MerkleConstants.TREE_HEIGHT = root) - function getNodeAtHeight(uint256 height) internal pure returns (bytes32 node) { - require(height <= MerkleConstants.TREE_HEIGHT, "Height out of bounds"); - uint256 start = height * MerkleConstants.HASH_SIZE; - bytes memory nodes = NODES; - assembly { - node := mload(add(add(nodes, 0x20), start)) - } - } -} diff --git a/cartesi-rollups/contracts/src/TestFungibleToken.sol b/cartesi-rollups/contracts/src/TestFungibleToken.sol deleted file mode 100644 index 97ac7d4ef..000000000 --- a/cartesi-rollups/contracts/src/TestFungibleToken.sol +++ /dev/null @@ -1,16 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.8; - -import {ERC20} from "@openzeppelin-contracts-5.5.0/token/ERC20/ERC20.sol"; - -contract TestFungibleToken is ERC20 { - constructor() ERC20("Fungible", "FUN") {} - - /// @notice Mint fungible tokens for oneself. - /// @param value The amount of fungible tokens to mint - function mint(uint256 value) external { - _mint(msg.sender, value); - } -} diff --git a/cartesi-rollups/contracts/src/TestMultiToken.sol b/cartesi-rollups/contracts/src/TestMultiToken.sol deleted file mode 100644 index 3f9e99ea8..000000000 --- a/cartesi-rollups/contracts/src/TestMultiToken.sol +++ /dev/null @@ -1,18 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.8; - -import {ERC1155} from "@openzeppelin-contracts-5.5.0/token/ERC1155/ERC1155.sol"; - -contract TestMultiToken is ERC1155 { - constructor() ERC1155("https://test-multi-token.com/{id}.json") {} - - /// @notice Mint multi-tokens for oneself. - /// @param tokenId The multi-token ID - /// @param value The amount of fungible tokens to mint - function mint(uint256 tokenId, uint256 value) external { - bytes memory data; - _mint(msg.sender, tokenId, value, data); - } -} diff --git a/cartesi-rollups/contracts/src/TestNonFungibleToken.sol b/cartesi-rollups/contracts/src/TestNonFungibleToken.sol deleted file mode 100644 index 9f05869f2..000000000 --- a/cartesi-rollups/contracts/src/TestNonFungibleToken.sol +++ /dev/null @@ -1,16 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.8; - -import {ERC721} from "@openzeppelin-contracts-5.5.0/token/ERC721/ERC721.sol"; - -contract TestNonFungibleToken is ERC721 { - constructor() ERC721("Non-fungible", "NFT") {} - - /// @notice Mint a non-fungible token for oneself. - /// @param tokenId The non-fungible token ID - function mint(uint256 tokenId) external { - _mint(msg.sender, tokenId); - } -} diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index fc74d3b04..ff954c416 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -3,163 +3,754 @@ pragma solidity ^0.8.22; import {Test} from "forge-std-1.9.6/src/Test.sol"; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; -import {DataAvailability} from "cartesi-rollups-contracts-2.2.0/src/common/DataAvailability.sol"; -import {ApplicationFactory} from "cartesi-rollups-contracts-2.2.0/src/dapp/ApplicationFactory.sol"; -import {IApplication} from "cartesi-rollups-contracts-2.2.0/src/dapp/IApplication.sol"; -import {IApplicationFactory} from "cartesi-rollups-contracts-2.2.0/src/dapp/IApplicationFactory.sol"; -import {IInputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/IInputBox.sol"; -import {InputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/InputBox.sol"; +import {Ownable} from "@openzeppelin-contracts-5.2.0/access/Ownable.sol"; +import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol"; + +import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; +import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; +import { + IOutputsMerkleRootValidator +} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {ApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/ApplicationFactory.sol"; +import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; +import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; +import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol"; +import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; +import {InputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/InputBox.sol"; +import {LibBinaryMerkleTree} from "cartesi-rollups-contracts-3.0.0/src/library/LibBinaryMerkleTree.sol"; +import {LibBytes} from "cartesi-rollups-contracts-3.0.0/src/library/LibBytes.sol"; +import {LibKeccak256} from "cartesi-rollups-contracts-3.0.0/src/library/LibKeccak256.sol"; +import {LibWithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/library/LibWithdrawalConfig.sol"; + +import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; +import {Memory} from "step/src/Memory.sol"; import {IDataProvider} from "prt-contracts/IDataProvider.sol"; +import {IStateTransition} from "prt-contracts/IStateTransition.sol"; +import {ITournament} from "prt-contracts/ITournament.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; -import {ITournament} from "prt-contracts/ITournamentFactory.sol"; +import { + CanonicalTournamentParametersProvider +} from "prt-contracts/arbitration-config/CanonicalTournamentParametersProvider.sol"; +import {CartesiStateTransition} from "prt-contracts/state-transition/CartesiStateTransition.sol"; +import {CmioStateTransition} from "prt-contracts/state-transition/CmioStateTransition.sol"; +import {RiscVStateTransition} from "prt-contracts/state-transition/RiscVStateTransition.sol"; +import {Tournament} from "prt-contracts/tournament/Tournament.sol"; +import {MultiLevelTournamentFactory} from "prt-contracts/tournament/factories/MultiLevelTournamentFactory.sol"; +import {Clock} from "prt-contracts/tournament/libs/Clock.sol"; +import {Time} from "prt-contracts/tournament/libs/Time.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; +import {Tree} from "prt-contracts/types/Tree.sol"; + import {DaveAppFactory} from "src/DaveAppFactory.sol"; import {IDaveAppFactory} from "src/IDaveAppFactory.sol"; import {IDaveConsensus} from "src/IDaveConsensus.sol"; -contract MockTournamentFactory is ITournamentFactory { - address tournamentAddress; +library LibExternalBinaryKeccak256MerkleTree { + using LibBinaryMerkleTree for bytes32[]; - function setAddress(address _addr) external { - tournamentAddress = _addr; - } - - function instantiate(Machine.Hash, IDataProvider) external view returns (ITournament) { - return ITournament(tournamentAddress); + function merkleRootAfterReplacement(bytes32[] calldata sibs, uint256 nodeIndex, bytes32 node) + external + pure + returns (bytes32) + { + return sibs.merkleRootAfterReplacement(nodeIndex, node, LibKeccak256.hashPair); } } -contract DaveConsensusFactoryTest is Test { +contract DaveAppFactoryTest is Test { + using LibExternalBinaryKeccak256MerkleTree for bytes32[]; + using LibWithdrawalConfig for WithdrawalConfig; + using LibBytes for bytes; + + error UnexpectedLogEmitter(Vm.Log log); + error UnexpectedLogTopic0(Vm.Log log); + + IInputBox _inputBox; IApplicationFactory _appFactory; + IStateTransition _stateTransition; + ITournamentFactory _tournamentFactory; IDaveAppFactory _daveAppFactory; - IInputBox _inputBox; - MockTournamentFactory _tournamentFactory; - Machine.Hash _initialMachineStateHash; + + Time.Duration constant MATCH_EFFORT = Time.Duration.wrap(5); + Time.Duration constant MAX_ALLOWANCE = Time.Duration.wrap(120); function setUp() external { _inputBox = new InputBox(); _appFactory = new ApplicationFactory(); - _tournamentFactory = new MockTournamentFactory(); + _stateTransition = new CartesiStateTransition(new RiscVStateTransition(), new CmioStateTransition()); + _tournamentFactory = new MultiLevelTournamentFactory( + new Tournament(), new CanonicalTournamentParametersProvider(MATCH_EFFORT, MAX_ALLOWANCE), _stateTransition + ); _daveAppFactory = new DaveAppFactory(_inputBox, _appFactory, _tournamentFactory); - _initialMachineStateHash = Machine.Hash.wrap(keccak256("foo")); } - function testNewDaveApp(address randomTournamentAddress, bytes32 templateHash, bytes32 salt) external { + function testNewDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external { + _randomizeBlockNumber(); + + (address precalculatedAppContractAddress, address precalculatedDaveConsensusAddress) = + _daveAppFactory.calculateDaveAppAddress(templateHash, withdrawalConfig, salt); + + vm.recordLogs(); + + try _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt) returns ( + IApplication appContract, IDaveConsensus daveConsensus + ) { + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertEq( + precalculatedAppContractAddress, + address(appContract), + "calculateDaveAppAddress(...)[0] != newDaveApp(...)[0]" + ); + + assertEq( + precalculatedDaveConsensusAddress, + address(daveConsensus), + "calculateDaveAppAddress(...)[1] != newDaveApp(...)[1]" + ); + + _testNewDaveAppSuccess(templateHash, withdrawalConfig, appContract, daveConsensus, logs); + + (precalculatedAppContractAddress, precalculatedDaveConsensusAddress) = + _daveAppFactory.calculateDaveAppAddress(templateHash, withdrawalConfig, salt); + + assertEq( + precalculatedAppContractAddress, + address(appContract), + "calculateDaveAppAddress(...)[0] != newDaveApp(...)[0]" + ); + + assertEq( + precalculatedDaveConsensusAddress, + address(daveConsensus), + "calculateDaveAppAddress(...)[1] != newDaveApp(...)[1]" + ); + } catch (bytes memory errorData) { + _testNewDaveAppFailure(withdrawalConfig, errorData); + return; + } + + // Cannot deploy an application with the same salt twice + try _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt) { + revert("second deterministic deployment did not revert"); + } catch (bytes memory errorData) { + assertEq( + errorData, new bytes(0), "second deterministic deployment did not revert with empty errorData data" + ); + } + } + + function testSettle( + bytes32 templateHash, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt, + bytes32 outputsMerkleRoot, + bytes[] calldata inputPayloads, + bool foreclose + ) external { + _randomizeBlockNumber(); + IApplication appContract; IDaveConsensus daveConsensus; + vm.assumeNoRevert(); + (appContract, daveConsensus) = _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt); + + bytes[] memory inputs = new bytes[](inputPayloads.length); + + for (uint256 i; i < inputPayloads.length; ++i) { + inputs[i] = _addInput(address(appContract), inputPayloads[i]); + } + + (,,, ITournament tournament) = daveConsensus.getCurrentSealedEpoch(); + + bytes32[] memory outputsMerkleRootProof = _randomProof(Memory.LOG2_MAX_SIZE); + bytes32 machineMerkleRoot = outputsMerkleRootProof.merkleRootAfterReplacement( + EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, + keccak256(abi.encode(outputsMerkleRoot)) + ); + + bytes32[] memory finalStateProof = _randomProof(tournament.tournamentArguments().commitmentArgs.height); + (bytes32 leftChild, bytes32 rightChild) = _getCommitmentChildren(machineMerkleRoot, finalStateProof); + bytes32 commitment = LibKeccak256.hashPair(leftChild, rightChild); + + address submitter = vm.randomAddress(); + uint256 bondValue = tournament.bondValue(); + uint256 callValue = vm.randomUint(bondValue, type(uint256).max); + + vm.deal(submitter, vm.randomUint(callValue, type(uint256).max)); + + uint256 balanceBefore = submitter.balance; + + vm.recordLogs(); + + vm.startPrank(submitter); + tournament.joinTournament{value: callValue}( + Machine.Hash.wrap(machineMerkleRoot), finalStateProof, Tree.Node.wrap(leftChild), Tree.Node.wrap(rightChild) + ); + vm.stopPrank(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + uint256 numOfCommitmentJoinedEvents; + + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(tournament)) { + if (log.topics[0] == ITournament.CommitmentJoined.selector) { + ++numOfCommitmentJoinedEvents; + assertEq(log.topics[1], bytes32(uint256(uint160(submitter)))); + bytes32 arg1; + bytes32 arg2; + (arg1, arg2) = abi.decode(log.data, (bytes32, bytes32)); + assertEq(arg1, commitment); + assertEq(arg2, machineMerkleRoot); + } else { + revert UnexpectedLogTopic0(log); + } + } else { + revert UnexpectedLogEmitter(log); + } + } + + assertEq(numOfCommitmentJoinedEvents, 1); + + assertFalse(tournament.isFinished()); + assertFalse(tournament.isClosed()); + + assertEq(tournament.getNewInnerTournamentCount(), 0); + assertEq(tournament.getMatchDeletedCount(), 0); + assertEq(tournament.getMatchAdvancedCount(), 0); + assertEq(tournament.getMatchCreatedCount(), 0); + assertEq(tournament.getCommitmentJoinedCount(), 1); + + assertEq(submitter.balance + callValue, balanceBefore, "joinTournament() keeps all Wei"); + + // Commitment clock and final state + { + Clock.State memory arg1; + Machine.Hash arg2; + (arg1, arg2) = tournament.getCommitment(Tree.Node.wrap(commitment)); + assertEq(Time.Duration.unwrap(arg1.allowance), Time.Duration.unwrap(MAX_ALLOWANCE)); + assertEq(Time.Instant.unwrap(arg1.startInstant), 0); // paused clock + assertEq(Machine.Hash.unwrap(arg2), machineMerkleRoot); + } + + // Arbitration result + { + (bool isFinished,,) = tournament.arbitrationResult(); + assertFalse(isFinished); + } + + // Check current sealed epoch + { + uint256 val1; + uint256 val2; + uint256 val3; + ITournament val4; + + (val1, val2, val3, val4) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 0); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, 0); // inputIndexUpperBound + assertEq(address(val4), address(tournament)); + } + + // Check epoch settlement readiness + { + bool val1; + uint256 val2; + + (val1, val2,) = daveConsensus.canSettle(); + + assertFalse(val1); // isFinished + assertEq(val2, 0); // epochNumber + } + + address settler = vm.randomAddress(); + + vm.startPrank(settler); + vm.expectRevert(IDaveConsensus.TournamentNotFinishedYet.selector); + daveConsensus.settle(0, outputsMerkleRoot, outputsMerkleRootProof); + vm.stopPrank(); + + vm.roll(vm.randomUint(vm.getBlockNumber() + Time.Duration.unwrap(MAX_ALLOWANCE), type(uint64).max)); + + assertTrue(tournament.isClosed()); + assertTrue(tournament.isFinished()); + + // Arbitration result { - address appContractAddress; - address daveConsensusAddress; - - // Pre-calculate app and Dave consensus contract addresses - (appContractAddress, daveConsensusAddress) = _daveAppFactory.calculateDaveAppAddress(templateHash, salt); - - // Deploy app and Dave consensus addresses - vm.recordLogs(); - _tournamentFactory.setAddress(randomTournamentAddress); - (appContract, daveConsensus) = _daveAppFactory.newDaveApp(templateHash, salt); - - // Check if addresses match those pre-calculated ones - assertEq(appContractAddress, address(appContract)); - assertEq(daveConsensusAddress, address(daveConsensus)); - } - - { - Vm.Log[] memory entries = vm.getRecordedLogs(); - - uint256 numOfDaveAppsCreated; - uint256 numOfAppsCreated; - - // Check logs - for (uint256 i; i < entries.length; ++i) { - Vm.Log memory entry = entries[i]; - - if ( - entry.emitter == address(_daveAppFactory) - && entry.topics[0] == IDaveAppFactory.DaveAppCreated.selector - ) { - ++numOfDaveAppsCreated; - address[] memory emittedAddresses = new address[](2); - (emittedAddresses[0], emittedAddresses[1]) = abi.decode(entry.data, (address, address)); - assertEq(emittedAddresses[0], address(appContract)); - assertEq(emittedAddresses[1], address(daveConsensus)); - } else if ( - entry.emitter == address(daveConsensus) && entry.topics[0] == IDaveConsensus.EpochSealed.selector - ) { - _checkEpochSealedData(entry.data, templateHash, randomTournamentAddress); - } else if ( - entry.emitter == address(_appFactory) - && entry.topics[0] == IApplicationFactory.ApplicationCreated.selector - ) { - ++numOfAppsCreated; - assertEq(address(uint160(uint256(entry.topics[1]))), address(0)); - ( - address appOwner, - bytes32 templateHashArg, - bytes memory dataAvailability, - address appContractAddressArg - ) = abi.decode(entry.data, (address, bytes32, bytes, address)); - - assertEq(appOwner, address(_daveAppFactory)); - assertEq(templateHashArg, templateHash); - assertEq(dataAvailability, abi.encodeCall(DataAvailability.InputBox, _inputBox)); - assertEq(appContractAddressArg, address(appContract)); + bool val1; + Tree.Node val2; + Machine.Hash val3; + + (val1, val2, val3) = tournament.arbitrationResult(); + assertTrue(val1); // isFinished + assertEq(Tree.Node.unwrap(val2), commitment); + assertEq(Machine.Hash.unwrap(val3), machineMerkleRoot); + } + + // Check current sealed epoch + { + uint256 val1; + uint256 val2; + uint256 val3; + ITournament val4; + + (val1, val2, val3, val4) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 0); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, 0); // inputIndexUpperBound + assertEq(address(val4), address(tournament)); + } + + // Check epoch settlement readiness + { + bool val1; + uint256 val2; + Tree.Node val3; + + (val1, val2, val3) = daveConsensus.canSettle(); + + assertTrue(val1); // isFinished + assertEq(val2, 0); // epochNumber + assertEq(Tree.Node.unwrap(val3), commitment); + } + + vm.startPrank(settler); + { + uint256 incorrectEpochNumber = vm.randomUint(1, type(uint256).max); + vm.expectRevert(_encodeIncorrectEpochNumber(incorrectEpochNumber, 0)); + daveConsensus.settle(incorrectEpochNumber, outputsMerkleRoot, outputsMerkleRootProof); + } + vm.stopPrank(); + + if (foreclose) { + vm.startPrank(appContract.getGuardian()); + appContract.foreclose(); + vm.stopPrank(); + } + + vm.recordLogs(); + + vm.startPrank(settler); + try daveConsensus.settle(0, outputsMerkleRoot, outputsMerkleRootProof) { + assertFalse(foreclose); + } catch (bytes memory errorData) { + (bool isValidError, bytes32 errorSelector,) = errorData.consumeBytes4(); + assertTrue(isValidError, "Expected error to contain a 4-byte selector"); + if (errorSelector == IApplicationChecker.ApplicationForeclosed.selector) { + assertTrue(foreclose, "Application was foreclosed prior to settlement attempt"); + assertTrue(appContract.isForeclosed(), "Application is indeed foreclosed"); + return; // do not continue test case + } else { + revert("Unexpected error"); + } + } + vm.stopPrank(); + + logs = vm.getRecordedLogs(); + + { + uint256 val1; + uint256 val2; + uint256 val3; + + (val1, val2, val3, tournament) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 1); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, inputs.length); // inputIndexUpperBound + } + + uint256 numOfTournamentCreatedEvents; + uint256 numOfEpochSealedEvents; + + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(_tournamentFactory)) { + if (log.topics[0] == ITournamentFactory.TournamentCreated.selector) { + ++numOfTournamentCreatedEvents; + address arg1; + arg1 = abi.decode(log.data, (address)); + assertEq(arg1, address(tournament)); + } else { + revert UnexpectedLogTopic0(log); } + } else if (log.emitter == address(daveConsensus)) { + if (log.topics[0] == IDaveConsensus.EpochSealed.selector) { + ++numOfEpochSealedEvents; + + uint256 arg1; + uint256 arg2; + uint256 arg3; + bytes32 arg4; + bytes32 arg5; + address arg6; + + (arg1, arg2, arg3, arg4, arg5, arg6) = + abi.decode(log.data, (uint256, uint256, uint256, bytes32, bytes32, address)); + + assertEq(arg1, 1); // epochNumber + assertEq(arg2, 0); // inputIndexLowerBound + assertEq(arg3, inputs.length); // inputIndexUpperBound + assertEq(arg4, machineMerkleRoot); // initialMachineStateHash + assertEq(arg5, outputsMerkleRoot); + assertEq(arg6, address(tournament)); + } + } else { + revert UnexpectedLogEmitter(log); } + } + + assertEq(numOfTournamentCreatedEvents, 1); + assertEq(numOfEpochSealedEvents, 1); - assertEq(numOfDaveAppsCreated, 1); - assertEq(numOfAppsCreated, 1); + assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), machineMerkleRoot); + assertTrue(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + + for (uint256 i; i < inputs.length; ++i) { + bytes memory input = inputs[i]; + assertNotEq(daveConsensus.provideMerkleRootOfInput(i, input), bytes32(0)); + } + + { + uint256 inputIndexWithinBounds = vm.randomUint(inputs.length, type(uint256).max); + uint256 inputLength = vm.randomUint(0, 100); + bytes memory input = vm.randomBytes(inputLength); + assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexWithinBounds, input), bytes32(0)); } + } + + function _testNewDaveAppSuccess( + bytes32 templateHash, + WithdrawalConfig calldata withdrawalConfig, + IApplication appContract, + IDaveConsensus daveConsensus, + Vm.Log[] memory logs + ) internal { + uint256 numOfOwnershipTransferredEvents; + uint256 numOfApplicationCreatedEvents; + uint256 numOfConsensusCreationEvents; + uint256 numOfTournamentCreatedEvents; + uint256 numOfEpochSealedEvents; + uint256 numOfOutputsMerkleRootValidatorChangedEvents; + uint256 numOfDaveAppCreatedEvents; + + ITournament tournament; // Check current sealed epoch - (uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, ITournament tournament) = - daveConsensus.getCurrentSealedEpoch(); - assertEq(epochNumber, 0); - assertEq(inputIndexLowerBound, 0); - assertEq(inputIndexUpperBound, 0); - assertEq(address(tournament), randomTournamentAddress); - - // Check getters + { + uint256 val1; + uint256 val2; + uint256 val3; + + (val1, val2, val3, tournament) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 0); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, 0); // inputIndexUpperBound + } + + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(appContract)) { + if (log.topics[0] == Ownable.OwnershipTransferred.selector) { + ++numOfOwnershipTransferredEvents; + if (numOfOwnershipTransferredEvents == 1) { + assertEq(log.topics[1], bytes32(0)); // previousOwner + assertEq(log.topics[2], bytes32(uint256(uint160(address(_daveAppFactory))))); // newOwner + } else { + assertEq(log.topics[1], bytes32(uint256(uint160(address(_daveAppFactory))))); // previousOwner + assertEq(log.topics[2], bytes32(0)); // newOwner + } + } else if (log.topics[0] == IApplication.OutputsMerkleRootValidatorChanged.selector) { + ++numOfOutputsMerkleRootValidatorChangedEvents; + address arg1 = abi.decode(log.data, (address)); + assertEq(arg1, address(daveConsensus)); // newOutputsMerkleRootValidator + } else { + revert UnexpectedLogTopic0(log); + } + } else if (log.emitter == address(_appFactory)) { + if (log.topics[0] == IApplicationFactory.ApplicationCreated.selector) { + ++numOfApplicationCreatedEvents; + assertEq(log.topics[1], bytes32(0)); // outputsMerkleRootValidator + address arg1; + bytes32 arg2; + bytes memory arg3; + WithdrawalConfig memory arg4; + address arg5; + (arg1, arg2, arg3, arg4, arg5) = + abi.decode(log.data, (address, bytes32, bytes, WithdrawalConfig, address)); + assertEq(arg1, address(_daveAppFactory)); // appOwner + assertEq(arg2, templateHash); + { + (bool isValid, bytes32 selector, bytes memory args) = arg3.consumeBytes4(); + assertTrue(isValid, "Expected data availability to be valid"); + assertEq(selector, DataAvailability.InputBox.selector); + address inputBoxAddress = abi.decode(args, (address)); + assertEq(inputBoxAddress, address(_inputBox)); + } + assertEq(abi.encode(arg4), abi.encode(withdrawalConfig)); + assertEq(arg5, address(appContract)); + } else { + revert UnexpectedLogTopic0(log); + } + } else if (log.emitter == address(daveConsensus)) { + if (log.topics[0] == IDaveConsensus.ConsensusCreation.selector) { + ++numOfConsensusCreationEvents; + + address arg1; + address arg2; + address arg3; + + (arg1, arg2, arg3) = abi.decode(log.data, (address, address, address)); + + assertEq(arg1, address(_inputBox)); + assertEq(arg2, address(appContract)); + assertEq(arg3, address(_tournamentFactory)); + } else if (log.topics[0] == IDaveConsensus.EpochSealed.selector) { + ++numOfEpochSealedEvents; + + uint256 arg1; + uint256 arg2; + uint256 arg3; + bytes32 arg4; + bytes32 arg5; + address arg6; + + (arg1, arg2, arg3, arg4, arg5, arg6) = + abi.decode(log.data, (uint256, uint256, uint256, bytes32, bytes32, address)); + + assertEq(arg1, 0); // epochNumber + assertEq(arg2, 0); // inputIndexLowerBound + assertEq(arg3, 0); // inputIndexUpperBound + assertEq(arg4, templateHash); // initialMachineStateHash + assertEq(arg5, bytes32(0)); // outputsMerkleRoot + assertEq(arg6, address(tournament)); // tournament + } else { + revert UnexpectedLogTopic0(log); + } + } else if (log.emitter == address(_daveAppFactory)) { + if (log.topics[0] == IDaveAppFactory.DaveAppCreated.selector) { + ++numOfDaveAppCreatedEvents; + address arg1; + address arg2; + (arg1, arg2) = abi.decode(log.data, (address, address)); + assertEq(arg1, address(appContract)); + assertEq(arg2, address(daveConsensus)); + } else { + revert UnexpectedLogTopic0(log); + } + } else if (log.emitter == address(_tournamentFactory)) { + if (log.topics[0] == ITournamentFactory.TournamentCreated.selector) { + ++numOfTournamentCreatedEvents; + address arg1; + arg1 = abi.decode(log.data, (address)); + assertEq(arg1, address(tournament)); + } else { + revert UnexpectedLogTopic0(log); + } + } else { + revert UnexpectedLogEmitter(log); + } + } + + assertEq(numOfOwnershipTransferredEvents, 2); + assertEq(numOfApplicationCreatedEvents, 1); + assertEq(numOfConsensusCreationEvents, 1); + assertEq(numOfTournamentCreatedEvents, 1); + assertEq(numOfEpochSealedEvents, 1); + assertEq(numOfOutputsMerkleRootValidatorChangedEvents, 1); + assertEq(numOfDaveAppCreatedEvents, 1); + + assertFalse(tournament.isFinished()); + assertFalse(tournament.isClosed()); + assertEq(tournament.getNewInnerTournamentCount(), 0); + assertEq(tournament.getMatchDeletedCount(), 0); + assertEq(tournament.getMatchAdvancedCount(), 0); + assertEq(tournament.getMatchCreatedCount(), 0); + assertEq(tournament.getCommitmentJoinedCount(), 0); + + // Tournament-level constants + { + uint64 levels; + uint64 level; + (levels, level,,) = tournament.tournamentLevelConstants(); + assertGe(levels, 1); + assertEq(level, 0); + } + + // Tournament-specific arguments + { + ITournament.TournamentArguments memory tournamentArgs; + tournamentArgs = tournament.tournamentArguments(); + assertEq(Machine.Hash.unwrap(tournamentArgs.commitmentArgs.initialHash), templateHash); + assertEq(tournamentArgs.commitmentArgs.startCycle, 0); + assertGe(tournamentArgs.levels, 1); + assertEq(tournamentArgs.level, 0); + assertEq(Time.Instant.unwrap(tournamentArgs.startInstant), vm.getBlockNumber()); + assertEq(Time.Duration.unwrap(tournamentArgs.allowance), Time.Duration.unwrap(MAX_ALLOWANCE)); + assertEq(Time.Duration.unwrap(tournamentArgs.maxAllowance), Time.Duration.unwrap(MAX_ALLOWANCE)); + assertEq(Time.Duration.unwrap(tournamentArgs.matchEffort), Time.Duration.unwrap(MATCH_EFFORT)); + assertEq(address(tournamentArgs.provider), address(daveConsensus)); + assertEq(address(tournamentArgs.stateTransition), address(_stateTransition)); + assertEq(tournamentArgs.tournamentFactory, address(_tournamentFactory)); + } + + // Arbitration result + { + (bool isFinished,,) = tournament.arbitrationResult(); + assertFalse(isFinished); + } + + // Check epoch settlement readiness + { + bool val1; + uint256 val2; + + (val1, val2,) = daveConsensus.canSettle(); + + assertFalse(val1); // isFinished + assertEq(val2, 0); // epochNumber + } + assertEq(address(daveConsensus.getInputBox()), address(_inputBox)); assertEq(address(daveConsensus.getApplicationContract()), address(appContract)); assertEq(address(daveConsensus.getTournamentFactory()), address(_tournamentFactory)); + assertEq(daveConsensus.getDeploymentBlockNumber(), vm.getBlockNumber()); + assertTrue(daveConsensus.supportsInterface(type(IERC165).interfaceId)); + assertTrue(daveConsensus.supportsInterface(type(IOutputsMerkleRootValidator).interfaceId)); + assertTrue(daveConsensus.supportsInterface(type(IDataProvider).interfaceId)); + assertFalse(daveConsensus.supportsInterface(0xffffffff)); + assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); + assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), bytes32(vm.randomUint()))); + + address notAppContract; + + while (true) { + notAppContract = vm.randomAddress(); + if (notAppContract != address(appContract)) { + break; + } + } + + vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); + daveConsensus.getLastFinalizedMachineMerkleRoot(notAppContract); + + vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); + daveConsensus.isOutputsMerkleRootValid(notAppContract, bytes32(vm.randomUint())); + + bytes4 unsupportedInterfaceId; + + while (true) { + unsupportedInterfaceId = vm.randomBytes4(); + if ( + unsupportedInterfaceId != type(IERC165).interfaceId + && unsupportedInterfaceId != type(IOutputsMerkleRootValidator).interfaceId + && unsupportedInterfaceId != type(IDataProvider).interfaceId + ) { + break; + } + } + + assertFalse(daveConsensus.supportsInterface(unsupportedInterfaceId)); { - address appContractAddress; - address daveConsensusAddress; + uint256 inputIndexWithinBounds = vm.randomUint(); + uint256 inputLength = vm.randomUint(0, 100); + bytes memory input = vm.randomBytes(inputLength); + assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexWithinBounds, input), bytes32(0)); + } + } + + function _testNewDaveAppFailure(WithdrawalConfig calldata withdrawalConfig, bytes memory errorData) internal pure { + (bool isValidError, bytes32 errorSelector, bytes memory errorArgs) = errorData.consumeBytes4(); + assertTrue(isValidError, "Expected error to contain a 4-byte selector"); + if (errorSelector == bytes4(keccak256("Error(string)"))) { + string memory errorMsg = abi.decode(errorArgs, (string)); + bytes32 errorMsgHash = keccak256(bytes(errorMsg)); + if (errorMsgHash == keccak256("Invalid withdrawal config")) { + assertFalse(withdrawalConfig.isValid(), "Expected withdrawal config to be invalid"); + } else { + revert("Unexpected error message"); + } + } else { + revert("Unexpected error"); + } + } + + function _randomizeBlockNumber() internal { + // We limit the block number by type(uint64).max because the PRT contracts + // use block numbers for time-keeping, and stores them as uint64 values. + // We also give some slack (the maximum tournament allowance) so we can + // fast-forward to a block in which the tournament is closed. + vm.roll(vm.randomUint(vm.getBlockNumber(), type(uint64).max - Time.Duration.unwrap(MAX_ALLOWANCE))); + } + + function _randomProof(uint256 n) internal returns (bytes32[] memory proof) { + proof = new bytes32[](n); + for (uint256 i; i < proof.length; ++i) { + proof[i] = bytes32(vm.randomUint()); + } + } - // Ensure the address remains the same when recalculated - (appContractAddress, daveConsensusAddress) = _daveAppFactory.calculateDaveAppAddress(templateHash, salt); + function _getCommitmentChildren(bytes32 machineMerkleRoot, bytes32[] memory proof) + internal + pure + returns (bytes32 leftChild, bytes32 rightChild) + { + leftChild = proof[proof.length - 1]; - assertEq(appContractAddress, address(appContract)); - assertEq(daveConsensusAddress, address(daveConsensus)); + rightChild = machineMerkleRoot; + for (uint256 i; i < proof.length - 1; ++i) { + rightChild = LibKeccak256.hashPair(proof[i], rightChild); } + } + + function _addInput(address appContract, bytes memory payload) internal returns (bytes memory input) { + uint256 index = _inputBox.getNumberOfInputs(appContract); + + vm.recordLogs(); - // Cannot deploy the same contract twice with the same salt - vm.expectRevert(); - _daveAppFactory.newDaveApp(templateHash, salt); + _inputBox.addInput(appContract, payload); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertGe(logs.length, 1, "No logs emitted on addInput()"); + + Vm.Log memory log = logs[0]; + + if (log.emitter == address(_inputBox)) { + if (log.topics[0] == IInputBox.InputAdded.selector) { + assertEq(log.topics[1], bytes32(uint256(uint160(appContract)))); + assertEq(log.topics[2], bytes32(index)); + return abi.decode(log.data, (bytes)); + } else { + revert UnexpectedLogTopic0(log); + } + } else { + revert UnexpectedLogEmitter(log); + } + } + + function _encodeApplicationMismatch(address expected, address obtained) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector(IDaveConsensus.ApplicationMismatch.selector, expected, obtained); } - function _checkEpochSealedData(bytes memory data, bytes32 templateHash, address randomTournamentAddress) + function _encodeIncorrectEpochNumber(uint256 received, uint256 actual) internal pure + returns (bytes memory encodedError) { - ( - uint256 epochNumber, - uint256 inputIndexLowerBound, - uint256 inputIndexUpperBound, - bytes32 initialMachineStateHash, - bytes32 outputTreeHash, - address tournamentAddress - ) = abi.decode(data, (uint256, uint256, uint256, bytes32, bytes32, address)); - - assertEq(epochNumber, 0); - assertEq(inputIndexLowerBound, 0); - assertEq(inputIndexUpperBound, 0); - assertEq(initialMachineStateHash, templateHash); - assertEq(outputTreeHash, bytes32(0)); - assertEq(tournamentAddress, randomTournamentAddress); + return abi.encodeWithSelector(IDaveConsensus.IncorrectEpochNumber.selector, received, actual); } } diff --git a/cartesi-rollups/contracts/test/DaveConsensus.t.sol b/cartesi-rollups/contracts/test/DaveConsensus.t.sol deleted file mode 100644 index 3b0626294..000000000 --- a/cartesi-rollups/contracts/test/DaveConsensus.t.sol +++ /dev/null @@ -1,642 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -import {Test} from "forge-std-1.9.6/src/Test.sol"; -import {Vm} from "forge-std-1.9.6/src/Vm.sol"; - -import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; -import {IERC165} from "@openzeppelin-contracts-5.5.0/utils/introspection/IERC165.sol"; - -import { - IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-2.2.0/src/consensus/IOutputsMerkleRootValidator.sol"; -import {IInputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/IInputBox.sol"; -import {InputBox} from "cartesi-rollups-contracts-2.2.0/src/inputs/InputBox.sol"; -import {LibMerkle32} from "cartesi-rollups-contracts-2.2.0/src/library/LibMerkle32.sol"; - -import {IDataProvider} from "prt-contracts/IDataProvider.sol"; -import {ITournament} from "prt-contracts/ITournament.sol"; -import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; -import {Clock} from "prt-contracts/tournament/libs/Clock.sol"; -import {Match} from "prt-contracts/tournament/libs/Match.sol"; -import {Time} from "prt-contracts/tournament/libs/Time.sol"; -import {Machine} from "prt-contracts/types/Machine.sol"; -import {Tree} from "prt-contracts/types/Tree.sol"; - -import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; -import {Memory} from "step/src/Memory.sol"; - -import {DaveConsensus} from "src/DaveConsensus.sol"; -import {IDaveConsensus} from "src/IDaveConsensus.sol"; -import {Merkle} from "src/Merkle.sol"; - -contract MerkleProxy { - using Merkle for bytes; - - function getMinLog2SizeOfDrive(bytes calldata data) external pure returns (uint256) { - return data.getMinLog2SizeOfDrive(); - } - - function getMerkleRootFromBytes(bytes calldata data, uint256 log2SizeOfDrive) external pure returns (bytes32) { - return data.getMerkleRootFromBytes(log2SizeOfDrive); - } -} - -contract MockTournament is ITournament { - Machine.Hash immutable _INITIAL_STATE; - IDataProvider immutable _PROVIDER; - bool _finished; - Tree.Node _winnerCommitment; - Machine.Hash _finalState; - - constructor(Machine.Hash initialState, IDataProvider provider) { - _INITIAL_STATE = initialState; - _PROVIDER = provider; - } - - function finish(Tree.Node winnerCommitment, Machine.Hash finalState) external { - _finished = true; - _winnerCommitment = winnerCommitment; - _finalState = finalState; - } - - function getInitialState() external view returns (Machine.Hash) { - return _INITIAL_STATE; - } - - function getProvider() external view returns (IDataProvider) { - return _PROVIDER; - } - - function arbitrationResult() - external - view - returns (bool finished, Tree.Node winnerCommitment, Machine.Hash finalState) - { - finished = _finished; - winnerCommitment = _winnerCommitment; - finalState = _finalState; - } - - function tryRecoveringBond() external pure override returns (bool) { - return true; - } - - error NotImplemented(); - - function bondValue() external pure override returns (uint256) { - revert NotImplemented(); - } - - function joinTournament(Machine.Hash, bytes32[] calldata, Tree.Node, Tree.Node) external payable override { - revert NotImplemented(); - } - - function advanceMatch(Match.Id calldata, Tree.Node, Tree.Node, Tree.Node, Tree.Node) external pure override { - revert NotImplemented(); - } - - function winMatchByTimeout(Match.Id calldata, Tree.Node, Tree.Node) external pure override { - revert NotImplemented(); - } - - function eliminateMatchByTimeout(Match.Id calldata) external pure override { - revert NotImplemented(); - } - - function sealInnerMatchAndCreateInnerTournament( - Match.Id calldata, - Tree.Node, - Tree.Node, - Machine.Hash, - bytes32[] calldata - ) external pure override { - revert NotImplemented(); - } - - function winInnerTournament(ITournament, Tree.Node, Tree.Node) external pure override { - revert NotImplemented(); - } - - function eliminateInnerTournament(ITournament) external pure override { - revert NotImplemented(); - } - - function sealLeafMatch(Match.Id calldata, Tree.Node, Tree.Node, Machine.Hash, bytes32[] calldata) - external - pure - override - { - revert NotImplemented(); - } - - function winLeafMatch(Match.Id calldata, Tree.Node, Tree.Node, bytes calldata) external pure override { - revert NotImplemented(); - } - - function canBeEliminated() external pure override returns (bool) { - revert NotImplemented(); - } - - function innerTournamentWinner() external pure override returns (bool, Tree.Node, Tree.Node, Clock.State memory) { - revert NotImplemented(); - } - - function tournamentArguments() external pure override returns (TournamentArguments memory) { - revert NotImplemented(); - } - - function canWinMatchByTimeout(Match.Id calldata) external pure override returns (bool) { - revert NotImplemented(); - } - - function getCommitment(Tree.Node) external pure override returns (Clock.State memory, Machine.Hash) { - revert NotImplemented(); - } - - function getMatch(Match.IdHash) external pure override returns (Match.State memory) { - revert NotImplemented(); - } - - function getMatchCycle(Match.IdHash) external pure override returns (uint256) { - revert NotImplemented(); - } - - function tournamentLevelConstants() external pure override returns (uint64, uint64, uint64, uint64) { - revert NotImplemented(); - } - - function isClosed() external pure override returns (bool) { - revert NotImplemented(); - } - - function isFinished() external view override returns (bool) { - return _finished; - } - - function timeFinished() external pure override returns (bool, Time.Instant) { - revert NotImplemented(); - } - - function getCommitmentJoinedCount() external pure override returns (uint256) { - return 0; - } - - function getMatchCreatedCount() external pure override returns (uint256) { - return 0; - } - - function getMatchAdvancedCount() external pure override returns (uint256) { - return 0; - } - - function getMatchDeletedCount() external pure override returns (uint256) { - return 0; - } - - function getNewInnerTournamentCount() external pure override returns (uint256) { - return 0; - } -} - -contract MockTournamentFactory is ITournamentFactory { - MockTournament[] _mockTournaments; - bytes32 _salt; - - error IndexOutOfBounds(); - - function instantiate(Machine.Hash initialState, IDataProvider provider) external returns (ITournament) { - MockTournament mockTournament = new MockTournament{salt: _salt}(initialState, provider); - _mockTournaments.push(mockTournament); - return mockTournament; - } - - function calculateTournamentAddress(Machine.Hash initialState, IDataProvider provider) - external - view - returns (address) - { - return Create2.computeAddress( - _salt, keccak256(abi.encodePacked(type(MockTournament).creationCode, abi.encode(initialState, provider))) - ); - } - - function setSalt(bytes32 salt) external { - _salt = salt; - } - - function getNumberOfMockTournaments() external view returns (uint256) { - return _mockTournaments.length; - } - - function getMockTournament(uint256 index) external view returns (MockTournament) { - if (index < _mockTournaments.length) { - return _mockTournaments[index]; - } else { - revert IndexOutOfBounds(); - } - } -} - -contract LibMerkle32Wrapper { - function merkleRootAfterReplacement(bytes32[] calldata sibs, uint256 index, bytes32 leaf) - external - pure - returns (bytes32) - { - return LibMerkle32.merkleRootAfterReplacement(sibs, index, leaf); - } -} - -contract DaveConsensusTest is Test { - IInputBox _inputBox; - MockTournamentFactory _mockTournamentFactory; - MerkleProxy _merkleProxy; - - function setUp() external { - _inputBox = new InputBox(); - _mockTournamentFactory = new MockTournamentFactory(); - _merkleProxy = new MerkleProxy(); - } - - function testMockTournamentFactory() external view { - assertEq(_mockTournamentFactory.getNumberOfMockTournaments(), 0); - } - - function testMockTournamentFactory(uint256 index) external { - vm.expectRevert(MockTournamentFactory.IndexOutOfBounds.selector); - _mockTournamentFactory.getMockTournament(index); - } - - function testConstructorAndSettle( - address appContract, - bytes32[3] calldata outputsMerkleRoots, - uint256[2] memory inputCounts, - bytes32[3] calldata salts, - Tree.Node[2] calldata winnerCommitments, - uint256 deploymentBlockNumber - ) external { - vm.roll(deploymentBlockNumber); - - for (uint256 i; i < 2; ++i) { - inputCounts[i] = bound(inputCounts[i], 0, 5); - } - - _addInputs(appContract, inputCounts[0]); - - (Machine.Hash state0,,) = _statesAndProofs(outputsMerkleRoots[0]); - - DaveConsensus daveConsensus; - MockTournament mockTournament; - - { - address daveConsensusAddress = _calculateNewDaveConsensus(appContract, state0, salts[0]); - - _mockTournamentFactory.setSalt(salts[1]); - address mockTournamentAddress = - _mockTournamentFactory.calculateTournamentAddress(state0, IDataProvider(daveConsensusAddress)); - - vm.expectEmit(daveConsensusAddress); - emit IDaveConsensus.ConsensusCreation(_inputBox, appContract, _mockTournamentFactory); - - vm.expectEmit(daveConsensusAddress); - emit IDaveConsensus.EpochSealed( - 0, 0, inputCounts[0], state0, bytes32(0), ITournament(mockTournamentAddress) - ); - - daveConsensus = _newDaveConsensus(appContract, state0, salts[0]); - - assertEq(address(daveConsensus), daveConsensusAddress); - assertEq(address(daveConsensus.getInputBox()), address(_inputBox)); - assertEq(daveConsensus.getApplicationContract(), appContract); - assertEq(address(daveConsensus.getTournamentFactory()), address(_mockTournamentFactory)); - assertEq(daveConsensus.getDeploymentBlockNumber(), deploymentBlockNumber); - - mockTournament = MockTournament(mockTournamentAddress); - } - - { - bool isFinished; - uint256 epochNumber; - - (isFinished, epochNumber,) = daveConsensus.canSettle(); - - assertFalse(isFinished); - assertEq(epochNumber, 0); - } - - { - uint256 epochNumber; - uint256 inputIndexLowerBound; - uint256 inputIndexUpperBound; - ITournament tournament; - - (epochNumber, inputIndexLowerBound, inputIndexUpperBound, tournament) = - daveConsensus.getCurrentSealedEpoch(); - - assertEq(epochNumber, 0); - assertEq(inputIndexLowerBound, 0); - assertEq(inputIndexUpperBound, inputCounts[0]); - assertEq(address(tournament), address(mockTournament)); - } - - assertEq(_mockTournamentFactory.getNumberOfMockTournaments(), 1); - assertEq(address(_mockTournamentFactory.getMockTournament(0)), address(mockTournament)); - - assertEq(Machine.Hash.unwrap(mockTournament.getInitialState()), Machine.Hash.unwrap(state0)); - assertEq(address(mockTournament.getProvider()), address(daveConsensus)); - - { - (bool isFinished,,) = mockTournament.arbitrationResult(); - - assertFalse(isFinished); - } - - (Machine.Hash state1,,) = _statesAndProofs(outputsMerkleRoots[1]); - mockTournament.finish(winnerCommitments[0], state1); - - { - bool isFinished; - Tree.Node winnerCommitmentTmp; - Machine.Hash finalStateTmp; - - (isFinished, winnerCommitmentTmp, finalStateTmp) = mockTournament.arbitrationResult(); - - assertTrue(isFinished); - assertEq(Tree.Node.unwrap(winnerCommitmentTmp), Tree.Node.unwrap(winnerCommitments[0])); - assertEq(Machine.Hash.unwrap(finalStateTmp), Machine.Hash.unwrap(state1)); - } - - { - bool isFinished; - uint256 epochNumber; - - (isFinished, epochNumber,) = daveConsensus.canSettle(); - - assertTrue(isFinished); - assertEq(epochNumber, 0); - } - - assertFalse(daveConsensus.isOutputsMerkleRootValid(appContract, outputsMerkleRoots[1])); - - _addInputs(appContract, inputCounts[1]); - - { - _mockTournamentFactory.setSalt(salts[2]); - address mockTournamentAddress = _mockTournamentFactory.calculateTournamentAddress(state1, daveConsensus); - - (, bytes32[] memory proof1, bytes32 leaf1) = _statesAndProofs(outputsMerkleRoots[1]); - - vm.expectEmit(address(daveConsensus)); - emit IDaveConsensus.EpochSealed( - 1, inputCounts[0], inputCounts[0] + inputCounts[1], state1, leaf1, ITournament(mockTournamentAddress) - ); - - daveConsensus.settle(0, leaf1, proof1); - - assertEq(_mockTournamentFactory.getNumberOfMockTournaments(), 2); - - mockTournament = _mockTournamentFactory.getMockTournament(1); - - assertEq(address(mockTournament), mockTournamentAddress); - } - - { - bool isFinished; - uint256 epochNumber; - - (isFinished, epochNumber,) = daveConsensus.canSettle(); - - assertFalse(isFinished); - assertEq(epochNumber, 1); - } - - { - uint256 epochNumber; - uint256 inputIndexLowerBound; - uint256 inputIndexUpperBound; - ITournament tournament; - - (epochNumber, inputIndexLowerBound, inputIndexUpperBound, tournament) = - daveConsensus.getCurrentSealedEpoch(); - - assertEq(epochNumber, 1); - assertEq(inputIndexLowerBound, inputCounts[0]); - assertEq(inputIndexUpperBound, inputCounts[0] + inputCounts[1]); - assertEq(address(tournament), address(mockTournament)); - } - - assertEq(Machine.Hash.unwrap(mockTournament.getInitialState()), Machine.Hash.unwrap(state1)); - assertEq(address(mockTournament.getProvider()), address(daveConsensus)); - - { - (bool isFinished,,) = mockTournament.arbitrationResult(); - - assertFalse(isFinished); - } - - assertTrue(daveConsensus.isOutputsMerkleRootValid(appContract, outputsMerkleRoots[1])); - - (Machine.Hash state2,,) = _statesAndProofs(outputsMerkleRoots[2]); - mockTournament.finish(winnerCommitments[1], state2); - - { - bool isFinished; - Tree.Node winnerCommitmentTmp; - Machine.Hash finalStateTmp; - - (isFinished, winnerCommitmentTmp, finalStateTmp) = mockTournament.arbitrationResult(); - - assertTrue(isFinished); - assertEq(Tree.Node.unwrap(winnerCommitmentTmp), Tree.Node.unwrap(winnerCommitments[1])); - assertEq(Machine.Hash.unwrap(finalStateTmp), Machine.Hash.unwrap(state2)); - } - } - - function testSettleReverts( - address appContract, - Machine.Hash[2] calldata states, - uint256[2] memory inputCounts, - bytes32[2] calldata salts, - uint256 wrongEpochNumber - ) external { - vm.assume(wrongEpochNumber != 0); - - for (uint256 i; i < 2; ++i) { - inputCounts[i] = bound(inputCounts[i], 0, 5); - } - - _addInputs(appContract, inputCounts[0]); - - _mockTournamentFactory.setSalt(salts[0]); - - DaveConsensus daveConsensus = _newDaveConsensus(appContract, states[0], salts[1]); - - _addInputs(appContract, inputCounts[1]); - - vm.expectRevert(abi.encodeWithSelector(IDaveConsensus.IncorrectEpochNumber.selector, wrongEpochNumber, 0)); - daveConsensus.settle(wrongEpochNumber, bytes32(0), new bytes32[](0)); - - vm.expectRevert(IDaveConsensus.TournamentNotFinishedYet.selector); - daveConsensus.settle(0, bytes32(0), new bytes32[](0)); - } - - function testProvideMerkleRootOfInput( - address appContract, - bytes[] calldata payloads, - uint256 inputIndexWithinBounds, - uint256 inputIndexOutsideBounds, - Machine.Hash initialState, - bytes32[2] calldata salts - ) external { - bytes[] memory inputs = _addInputs(appContract, payloads); - - _mockTournamentFactory.setSalt(salts[0]); - - DaveConsensus daveConsensus = _newDaveConsensus(appContract, initialState, salts[1]); - - if (inputs.length > 0) { - inputIndexWithinBounds = bound(inputIndexWithinBounds, 0, inputs.length - 1); - bytes memory input = inputs[inputIndexWithinBounds]; - bytes32 root = daveConsensus.provideMerkleRootOfInput(inputIndexWithinBounds, input); - uint256 log2SizeOfDrive = _merkleProxy.getMinLog2SizeOfDrive(input); - assertEq(root, _merkleProxy.getMerkleRootFromBytes(input, log2SizeOfDrive)); - } - - { - inputIndexOutsideBounds = bound(inputIndexOutsideBounds, inputs.length, type(uint256).max); - bytes32 root = daveConsensus.provideMerkleRootOfInput(inputIndexOutsideBounds, new bytes(0)); - assertEq(root, bytes32(0)); - } - } - - function testErc165(address appContract, Machine.Hash initialState, bytes32 salt, bytes4 unsupportedInterfaceId) - external - { - DaveConsensus daveConsensus = _newDaveConsensus(appContract, initialState, salt); - - // List the ID of all interfaces supported by `DaveConsensus` - bytes4[] memory supportedInterfaces = new bytes4[](3); - supportedInterfaces[0] = type(IERC165).interfaceId; - supportedInterfaces[1] = type(IDataProvider).interfaceId; - supportedInterfaces[2] = type(IOutputsMerkleRootValidator).interfaceId; - - // For each supported interface ID, ensure `supportsInterface` returns true - // Also, make sure the fuzzy parameter `unsupportedInterfaceId` is distinct from them - for (uint256 i; i < supportedInterfaces.length; ++i) { - bytes4 interfaceId = supportedInterfaces[i]; - assertTrue(daveConsensus.supportsInterface(interfaceId)); - vm.assume(unsupportedInterfaceId != interfaceId); - } - - // Finally, ensure that any other interface ID is explicitly unsupported - assertFalse(daveConsensus.supportsInterface(unsupportedInterfaceId)); - } - - function testIsOutputsMerkleRootValid( - address appContract, - Machine.Hash initialState, - bytes32 salt, - address otherAppContract, - bytes32 outputsMerkleRoot - ) external { - vm.assume(appContract != otherAppContract); - - DaveConsensus daveConsensus = _newDaveConsensus(appContract, initialState, salt); - - vm.expectRevert(_encodeApplicationMismatch(appContract, otherAppContract)); - daveConsensus.isOutputsMerkleRootValid(otherAppContract, outputsMerkleRoot); - - assertFalse(daveConsensus.isOutputsMerkleRootValid(appContract, outputsMerkleRoot)); - } - - function _addInputs(address appContract, uint256 n) internal { - for (uint256 i; i < n; ++i) { - _inputBox.addInput(appContract, new bytes(0)); - } - } - - function _addInputs(address appContract, bytes[] calldata payloads) internal returns (bytes[] memory) { - bytes32[] memory inputHashes = new bytes32[](payloads.length); - - vm.recordLogs(); - - for (uint256 i; i < payloads.length; ++i) { - inputHashes[i] = _inputBox.addInput(appContract, payloads[i]); - } - - Vm.Log[] memory entries = vm.getRecordedLogs(); - - bytes[] memory inputs = new bytes[](payloads.length); - - for (uint256 i; i < entries.length; ++i) { - Vm.Log memory entry = entries[i]; - assertEq(entry.emitter, address(_inputBox)); - assertEq(entry.topics[0], IInputBox.InputAdded.selector); - assertEq(entry.topics[1], bytes32(uint256(uint160(appContract)))); - assertEq(entry.topics[2], bytes32(i)); - bytes memory input = abi.decode(entry.data, (bytes)); - assertEq(keccak256(input), inputHashes[i]); - inputs[i] = input; - } - - return inputs; - } - - function _calculateNewDaveConsensus(address appContract, Machine.Hash initialState, bytes32 salt) - internal - view - returns (address) - { - return Create2.computeAddress( - salt, - keccak256( - abi.encodePacked( - type(DaveConsensus).creationCode, - abi.encode(_inputBox, appContract, _mockTournamentFactory, initialState) - ) - ) - ); - } - - function _newDaveConsensus(address appContract, Machine.Hash initialState, bytes32 salt) - internal - returns (DaveConsensus) - { - return new DaveConsensus{salt: salt}(_inputBox, appContract, _mockTournamentFactory, initialState); - } - - function _statesAndProofs(bytes32 outputsMerkleRoot) private returns (Machine.Hash, bytes32[] memory, bytes32) { - uint256 levels = Memory.LOG2_MAX_SIZE; - bytes32[] memory siblings = new bytes32[](levels); - - bytes32 leaf = keccak256(abi.encode(outputsMerkleRoot)); - bytes32 current = leaf; - for (uint256 i = 0; i < levels; i++) { - siblings[i] = current; - current = keccak256(abi.encodePacked(current, current)); - } - - bytes32 root = new LibMerkle32Wrapper() - .merkleRootAfterReplacement( - siblings, EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, leaf - ); - assertEq(current, root); - - return (Machine.Hash.wrap(current), siblings, outputsMerkleRoot); - } - - /// @notice Encode an `ApplicationMismatch` error. - /// @param expected The expected application contract address (the one provided through the constructor) - /// @param obtained The application contract address received by the function - /// @return encodedError The ABI-encoded Solidity error - function _encodeApplicationMismatch(address expected, address obtained) - internal - pure - returns (bytes memory encodedError) - { - return abi.encodeWithSelector(IDaveConsensus.ApplicationMismatch.selector, expected, obtained); - } -} diff --git a/cartesi-rollups/contracts/test/Math.t.sol b/cartesi-rollups/contracts/test/Math.t.sol deleted file mode 100644 index 01ee254d3..000000000 --- a/cartesi-rollups/contracts/test/Math.t.sol +++ /dev/null @@ -1,84 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -import {Test} from "forge-std-1.9.6/src/Test.sol"; - -import {Math} from "src/Math.sol"; - -library NaiveMath { - function ctz(uint256 x) internal pure returns (uint256) { - uint256 n = 256; - while (x != 0) { - --n; - x <<= 1; - } - return n; - } - - function clz(uint256 x) internal pure returns (uint256) { - uint256 n = 256; - while (x != 0) { - --n; - x >>= 1; - } - return n; - } - - function log2clp(uint256 x) internal pure returns (uint256) { - for (uint256 i; i < 256; ++i) { - if (x <= (1 << i)) { - return i; - } - } - return 256; - } -} - -contract MathTest is Test { - function testCtz() external pure { - assertEq(Math.ctz(0), 256); - assertEq(Math.ctz(type(uint256).max), 0); - for (uint256 i; i < 256; ++i) { - assertEq(Math.ctz(1 << i), i); - for (uint256 j = i + 1; j < 256; ++j) { - assertEq(Math.ctz((1 << i) | (1 << j)), i); - } - } - } - - function testCtz(uint256 x) external pure { - assertEq(Math.ctz(x), NaiveMath.ctz(x)); - } - - function testClz() external pure { - assertEq(Math.clz(0), 256); - assertEq(Math.clz(type(uint256).max), 0); - for (uint256 i; i < 256; ++i) { - assertEq(Math.clz(1 << i), 255 - i); - for (uint256 j; j < i; ++j) { - assertEq(Math.clz((1 << i) | (1 << j)), 255 - i); - } - } - } - - function testClz(uint256 x) external pure { - assertEq(Math.clz(x), NaiveMath.clz(x)); - } - - function testLog2Clp() external pure { - assertEq(Math.log2clp(0), 0); - assertEq(Math.log2clp(type(uint256).max), 256); - for (uint256 i; i < 256; ++i) { - assertEq(Math.log2clp(1 << i), i); - for (uint256 j; j < i; ++j) { - assertEq(Math.log2clp((1 << i) | (1 << j)), i + 1); - } - } - } - - function testLog2Clp(uint256 x) external pure { - assertEq(Math.log2clp(x), NaiveMath.log2clp(x)); - } -} diff --git a/cartesi-rollups/contracts/test/Merkle.t.sol b/cartesi-rollups/contracts/test/Merkle.t.sol deleted file mode 100644 index dc8904800..000000000 --- a/cartesi-rollups/contracts/test/Merkle.t.sol +++ /dev/null @@ -1,379 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.0; - -import {Test} from "forge-std-1.9.6/src/Test.sol"; - -import {Merkle} from "src/Merkle.sol"; -import {MerkleConstants} from "src/MerkleConstants.sol"; -import {PristineMerkleTree} from "src/PristineMerkleTree.sol"; - -library PristineMerkleTreeWrapper { - function getNodeAtHeight(uint256 height) external pure returns (bytes32) { - return PristineMerkleTree.getNodeAtHeight(height); - } -} - -library MerkleWrapper { - function join(bytes32 a, bytes32 b) external pure returns (bytes32) { - return Merkle.join(a, b); - } - - function getRootAfterReplacementInDrive( - uint256 position, - uint256 log2SizeOfReplacement, - uint256 log2SizeOfDrive, - bytes32 replacement, - bytes32[] calldata siblings - ) external pure returns (bytes32) { - return Merkle.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ); - } - - function getMinLog2SizeOfDrive(bytes calldata data) external pure returns (uint256) { - return Merkle.getMinLog2SizeOfDrive(data); - } - - function getMerkleRootFromBytes(bytes calldata data, uint256 log2SizeOfDrive) external pure returns (bytes32) { - return Merkle.getMerkleRootFromBytes(data, log2SizeOfDrive); - } - - function getHashOfLeafAtIndex(bytes calldata data, uint256 leafIndex) external pure returns (bytes32) { - return Merkle.getHashOfLeafAtIndex(data, leafIndex); - } -} - -contract MerkleTest is Test { - uint256 constant LEAF_SIZE = (1 << MerkleConstants.LOG2_LEAF_SIZE); - bytes32 constant WORD_0 = keccak256("foo"); - bytes32 constant WORD_1 = keccak256("bar"); - bytes1 constant WORD_2 = hex"ff"; - uint256 constant HEIGHT = 2; - bytes32 constant LEAF_0 = keccak256(abi.encode(WORD_0)); - bytes32 constant LEAF_1 = keccak256(abi.encode(WORD_1)); - bytes32 constant LEAF_2 = keccak256(abi.encode(WORD_2)); - bytes32 constant LEAF_3 = keccak256(abi.encode(bytes32(0))); - bytes32 constant NODE_01 = keccak256(abi.encode(LEAF_0, LEAF_1)); - bytes32 constant NODE_23 = keccak256(abi.encode(LEAF_2, LEAF_3)); - bytes32 constant ROOT = keccak256(abi.encode(NODE_01, NODE_23)); - uint256 constant MAX_HEIGHT = MerkleConstants.LOG2_MEMORY_SIZE - MerkleConstants.LOG2_LEAF_SIZE; - - function testJoinEquivalence(bytes32 a, bytes32 b) external pure { - assertEq(MerkleWrapper.join(a, b), keccak256(abi.encodePacked(a, b))); - } - - function testPristineMerkleTree() external pure { - bytes32 node = keccak256(abi.encode(0)); - for (uint256 height; height <= MerkleConstants.TREE_HEIGHT; ++height) { - assertEq(PristineMerkleTree.getNodeAtHeight(height), node); - node = MerkleWrapper.join(node, node); - } - } - - function testPristineMerkleTreeRevert(uint256 height) external { - height = bound(height, MerkleConstants.TREE_HEIGHT + 1, type(uint256).max); - vm.expectRevert("Height out of bounds"); - PristineMerkleTreeWrapper.getNodeAtHeight(height); - } - - function testGetRootAfterReplacementInDrive() external pure { - { - uint256 log2SizeOfReplacement = MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 0 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = LEAF_0; - bytes32[] memory siblings = new bytes32[](2); - siblings[0] = LEAF_1; - siblings[1] = NODE_23; - - // +------+ - // | root | - // +---+--+ - // | - // +-----------+----------+ - // | | - // +---+--+ +--+---+ - // | | | sib1 | - // +---+--+ +------+ - // | - // +-----------+----------+ - // | | - // +---+--+ +--+---+ - // | repl | | sib0 | - // +------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - { - uint256 log2SizeOfReplacement = MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 1 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = LEAF_1; - bytes32[] memory siblings = new bytes32[](2); - siblings[0] = LEAF_0; - siblings[1] = NODE_23; - - // +------+ - // | root | - // +---+--+ - // | - // +-----------+----------+ - // | | - // +---+--+ +--+---+ - // | | | sib1 | - // +---+--+ +------+ - // | - // +-----------+----------+ - // | | - // +---+--+ +--+---+ - // | sib0 | | repl | - // +------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - { - uint256 log2SizeOfReplacement = MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 2 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = LEAF_2; - bytes32[] memory siblings = new bytes32[](2); - siblings[0] = LEAF_3; - siblings[1] = NODE_01; - - // +------+ - // | root | - // +--+---+ - // | - // +----+---------+ - // | | - // +---+---+ +---+--+ - // | sib1 | | | - // +-------+ +--+---+ - // | - // +---------+-----------+ - // | | - // +---+---+ +---+--+ - // | repl | | sib0 | - // +-------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - { - uint256 log2SizeOfReplacement = MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 3 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = LEAF_3; - bytes32[] memory siblings = new bytes32[](2); - siblings[0] = LEAF_2; - siblings[1] = NODE_01; - - // +------+ - // | root | - // +--+---+ - // | - // +----+---------+ - // | | - // +---+---+ +---+--+ - // | sib1 | | | - // +-------+ +--+---+ - // | - // +---------+-----------+ - // | | - // +---+---+ +---+--+ - // | sib0 | | repl | - // +-------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - { - uint256 log2SizeOfReplacement = 1 + MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 0 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = NODE_01; - bytes32[] memory siblings = new bytes32[](1); - siblings[0] = NODE_23; - - // +------+ - // | root | - // +--+---+ - // | - // +----+---------+ - // | | - // +---+---+ +---+--+ - // | repl | | sib0 | - // +-------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - { - uint256 log2SizeOfReplacement = 1 + MerkleConstants.LOG2_LEAF_SIZE; - uint256 position = 1 << log2SizeOfReplacement; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = NODE_23; - bytes32[] memory siblings = new bytes32[](1); - siblings[0] = NODE_01; - - // +------+ - // | root | - // +--+---+ - // | - // +----+---------+ - // | | - // +---+---+ +---+--+ - // | sib0 | | repl | - // +-------+ +------+ - - assertEq( - ROOT, - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ) - ); - } - } - - function testGetRootAfterReplacementInDriveRevertsPositionNotAligned(uint256 position) external { - vm.assume(position % (1 << MerkleConstants.LOG2_LEAF_SIZE) != 0); - { - uint256 log2SizeOfReplacement = MerkleConstants.LOG2_LEAF_SIZE; - uint256 log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + HEIGHT; - bytes32 replacement = LEAF_0; - bytes32[] memory siblings = new bytes32[](2); - siblings[0] = LEAF_1; - siblings[1] = NODE_23; - vm.expectRevert("Position is not aligned"); - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ); - } - } - - function testGetRootAfterAnyReplacementInDriveRevertsProofLengthDoesNotMatch( - uint256 position, - uint256 log2SizeOfReplacement, - uint256 log2SizeOfDrive, - bytes32 replacement, - bytes32[] calldata siblings - ) external { - log2SizeOfDrive = bound(log2SizeOfDrive, MerkleConstants.LOG2_LEAF_SIZE, MerkleConstants.LOG2_MEMORY_SIZE); - log2SizeOfReplacement = bound(log2SizeOfReplacement, MerkleConstants.LOG2_LEAF_SIZE, log2SizeOfDrive); - vm.assume(siblings.length != log2SizeOfDrive - log2SizeOfReplacement); - position = (position >> log2SizeOfReplacement) << log2SizeOfReplacement; - vm.expectRevert("Proof length does not match"); - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ); - } - - function testGetRootAfterAnyReplacementInDrive( - uint256 position, - uint256 log2SizeOfReplacement, - uint256 log2SizeOfDrive, - bytes32 replacement - ) external pure { - log2SizeOfDrive = bound(log2SizeOfDrive, MerkleConstants.LOG2_LEAF_SIZE, MerkleConstants.LOG2_MEMORY_SIZE); - log2SizeOfReplacement = bound(log2SizeOfReplacement, MerkleConstants.LOG2_LEAF_SIZE, log2SizeOfDrive); - bytes32[] memory siblings = new bytes32[](log2SizeOfDrive - log2SizeOfReplacement); - position = (position >> log2SizeOfReplacement) << log2SizeOfReplacement; - MerkleWrapper.getRootAfterReplacementInDrive( - position, log2SizeOfReplacement, log2SizeOfDrive, replacement, siblings - ); - } - - function testGetMinLog2SizeOfDrive(bytes calldata data) external pure { - uint256 log2SizeOfDrive = MerkleWrapper.getMinLog2SizeOfDrive(data); - assertLe(data.length, 1 << log2SizeOfDrive); - if (data.length <= LEAF_SIZE) { - assertEq(log2SizeOfDrive, MerkleConstants.LOG2_LEAF_SIZE); - } else { - assertGt(data.length, 1 << (log2SizeOfDrive - 1)); - } - MerkleWrapper.getMerkleRootFromBytes(data, log2SizeOfDrive); - } - - function testGetMerkleRootFromEmptyBytes(uint256 log2SizeOfDrive) external pure { - bytes memory data; - uint256 minLog2SizeOfDrive = MerkleWrapper.getMinLog2SizeOfDrive(data); - uint256 maxLog2SizeOfDrive = MerkleConstants.LOG2_MEMORY_SIZE; - log2SizeOfDrive = bound(log2SizeOfDrive, minLog2SizeOfDrive, maxLog2SizeOfDrive); - assertEq( - MerkleWrapper.getMerkleRootFromBytes(data, log2SizeOfDrive), - PristineMerkleTree.getNodeAtHeight(log2SizeOfDrive - MerkleConstants.LOG2_LEAF_SIZE) - ); - } - - function testGetMerkleRootFromBytes() external pure { - bytes memory data = _getTestData(); - bytes32 root = ROOT; - for (uint256 i; MerkleConstants.LOG2_LEAF_SIZE + HEIGHT + i <= MerkleConstants.LOG2_MEMORY_SIZE; ++i) { - assertEq(MerkleWrapper.getMerkleRootFromBytes(data, MerkleConstants.LOG2_LEAF_SIZE + HEIGHT + i), root); - root = MerkleWrapper.join(root, PristineMerkleTree.getNodeAtHeight(HEIGHT + i)); - } - } - - function testGetMerkleRootFromBytesRevertDriveSmallerThanLeaf(uint256 log2SizeOfDrive) external { - log2SizeOfDrive = bound(log2SizeOfDrive, 0, MerkleConstants.LOG2_LEAF_SIZE - 1); - vm.expectRevert("Drive smaller than leaf"); - MerkleWrapper.getMerkleRootFromBytes(_getTestData(), log2SizeOfDrive); - } - - function testGetMerkleRootFromBytesRevertDataLargerThanDrive(uint256 log2SizeOfDrive) external { - log2SizeOfDrive = MerkleConstants.LOG2_LEAF_SIZE + bound(log2SizeOfDrive, 0, HEIGHT - 1); - vm.expectRevert("Data larger than drive"); - MerkleWrapper.getMerkleRootFromBytes(_getTestData(), log2SizeOfDrive); - } - - function testGetMerkleRootFromBytesRevertDriveLargerThanMemory(uint256 log2SizeOfDrive) external { - log2SizeOfDrive = bound(log2SizeOfDrive, MerkleConstants.LOG2_MEMORY_SIZE + 1, type(uint256).max); - vm.expectRevert("Drive larger than memory"); - MerkleWrapper.getMerkleRootFromBytes(_getTestData(), log2SizeOfDrive); - } - - function testGetHashOfLeafAtIndex() external pure { - bytes memory data = _getTestData(); - assertEq(MerkleWrapper.getHashOfLeafAtIndex(data, 0), LEAF_0); - assertEq(MerkleWrapper.getHashOfLeafAtIndex(data, 1), LEAF_1); - assertEq(MerkleWrapper.getHashOfLeafAtIndex(data, 2), LEAF_2); - } - - function testGetHashOfLeafAtIndex(bytes calldata data, uint256 index) external pure { - index = bound(index, _getNumOfLeaves(data.length), type(uint256).max); - bytes32 leafHash = PristineMerkleTree.getNodeAtHeight(0); - assertEq(MerkleWrapper.getHashOfLeafAtIndex(data, index), leafHash); - } - - function _getTestData() internal pure returns (bytes memory) { - return abi.encodePacked(WORD_0, WORD_1, WORD_2); - } - - function _getNumOfLeaves(uint256 length) internal pure returns (uint256) { - return (length + LEAF_SIZE - 1) / LEAF_SIZE; - } -} diff --git a/cartesi-rollups/node/blockchain-reader/src/lib.rs b/cartesi-rollups/node/blockchain-reader/src/lib.rs index 98e537ce4..ce95bf1cc 100644 --- a/cartesi-rollups/node/blockchain-reader/src/lib.rs +++ b/cartesi-rollups/node/blockchain-reader/src/lib.rs @@ -753,8 +753,10 @@ mod blockchain_reader_tests { let (handle, mut state_manager) = state_access(); - // Note that inputbox is deployed with 1 input already - // add inputs to epoch 0 + let input_count_0 = 1; + + // Note that one input has been sent already + // add inputs to epoch 1 let input_count_1 = 2; add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_1).await?; @@ -782,14 +784,19 @@ mod blockchain_reader_tests { }) }); - read_inputs_from_db_until_count(&mut state_manager, 0, 1).await?; - read_inputs_from_db_until_count(&mut state_manager, 1, input_count_1).await?; + read_inputs_from_db_until_count(&mut state_manager, 0, 0).await?; + read_inputs_from_db_until_count(&mut state_manager, 1, input_count_0 + input_count_1) + .await?; - // add inputs ttest_blockchain_readero epoch 1 + // add inputs to epoch 1 let input_count_2 = 3; add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_2).await?; - read_inputs_from_db_until_count(&mut state_manager, 1, input_count_1 + input_count_2) - .await?; + read_inputs_from_db_until_count( + &mut state_manager, + 1, + input_count_0 + input_count_1 + input_count_2, + ) + .await?; // add more inputs to epoch 1 let input_count_3 = 3; @@ -797,7 +804,7 @@ mod blockchain_reader_tests { read_inputs_from_db_until_count( &mut state_manager, 1, - input_count_1 + input_count_2 + input_count_3, + input_count_0 + input_count_1 + input_count_2 + input_count_3, ) .await?; diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index b69ea5639..fc9c49ce1 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -8,7 +8,7 @@ use alloy::{ providers::{DynProvider, Provider, ProviderBuilder}, signers::{Signer, local::PrivateKeySigner}, }; -use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory; +use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory::{self, WithdrawalConfig}; use cartesi_rollups_contracts::i_input_box::IInputBox; use serde::Deserialize; use std::{ @@ -84,26 +84,34 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A buffer }; + let withdrawal_config = WithdrawalConfig { + guardian: Default::default(), + log2LeavesPerAccount: Default::default(), + log2MaxNumOfAccounts: Default::default(), + accountsDriveStartIndex: Default::default(), + withdrawalOutputBuilder: Default::default(), + }; + let salt = FixedBytes::default(); let dave_app_factory_contract = IDaveAppFactory::new(dave_app_factory, &provider); let (app, consensus) = dave_app_factory_contract - .calculateDaveAppAddress(initial_hash.into(), salt) + .calculateDaveAppAddress(initial_hash.into(), withdrawal_config.clone(), salt) .call() .await .expect("failed to calculate Dave app addresses") .try_into() .unwrap(); - IInputBox::new(input_box, &provider) - .addInput(app, "Hello, world!".into()) + dave_app_factory_contract + .newDaveApp(initial_hash.into(), withdrawal_config.clone(), salt) .send() .await? .watch() .await?; - dave_app_factory_contract - .newDaveApp(initial_hash.into(), salt) + IInputBox::new(input_box, &provider) + .addInput(app, "Hello, world!".into()) .send() .await? .watch() diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 63336e6a1..6b8402719 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -280,8 +280,10 @@ function Reader:balance(address) end function Reader:calculate_dave_app_address(template_hash, salt) - local sig = "calculateDaveAppAddress(bytes32,bytes32)(address,address)" - local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, salt }) + local sig = "calculateDaveAppAddress(bytes32,(address,uint8,uint8,uint64,address),bytes32)(address,address)" + local address_zero = "0x" .. string.rep("00", 20) + local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) + local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, withdrawal_config, salt }) assert(#ret == 2) return table.unpack(ret) end diff --git a/prt/tests/rollups/dave/sender.lua b/prt/tests/rollups/dave/sender.lua index 69e43f5fa..9482d26ff 100644 --- a/prt/tests/rollups/dave/sender.lua +++ b/prt/tests/rollups/dave/sender.lua @@ -113,11 +113,13 @@ function Sender:tx_add_inputs(inputs) end function Sender:tx_new_dave_app(template_hash, salt) - local sig = "newDaveApp(bytes32,bytes32)" + local sig = "newDaveApp(bytes32,(address,uint8,uint8,uint64,address),bytes32)" + local address_zero = "0x" .. string.rep("00", 20) + local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) return self:_send_tx( self.dave_app_factory_address, sig, - { template_hash, salt } + { template_hash, withdrawal_config, salt } ) end diff --git a/prt/tests/rollups/test_cases/big_input.lua b/prt/tests/rollups/test_cases/big_input.lua index d49364979..319cc1683 100755 --- a/prt/tests/rollups/test_cases/big_input.lua +++ b/prt/tests/rollups/test_cases/big_input.lua @@ -10,7 +10,7 @@ local conversion = require "utils.conversion" -- Main Execution env.spawn_blockchain {env.sample_inputs[1]} local first_epoch = assert(env.reader:read_epochs_sealed()[1]) -assert(first_epoch.input_upper_bound == 1) -- there's one input for epoch 0 already! +assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! -- The reduction was 10, however it causes decoding error on some machines -- Using 13 to is still a big input but passes the test diff --git a/prt/tests/rollups/test_cases/gc_match.lua b/prt/tests/rollups/test_cases/gc_match.lua index 0c9a310bf..4e5ef32c6 100644 --- a/prt/tests/rollups/test_cases/gc_match.lua +++ b/prt/tests/rollups/test_cases/gc_match.lua @@ -11,18 +11,28 @@ local CommitmentBuilder = require "computation.commitment" -- Main Execution env.spawn_blockchain {env.sample_inputs[1]} local first_epoch = assert(env.reader:read_epochs_sealed()[1]) -assert(first_epoch.input_upper_bound == 1) -- there's one input for epoch 0 already! +assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! +-- Add 3 inputs to epoch 1 +env.sender:tx_add_inputs { env.sample_inputs[1], env.sample_inputs[1], env.sample_inputs[1] } + +-- Spawn Dave node +env.spawn_node() + +-- advance such that epoch 0 is finished +local second_epoch = env.roll_epoch() +assert(second_epoch.epoch_number == 1) +assert(second_epoch.input_upper_bound == 4) -- there are 4 inputs for epoch 1! local inputs = {} -for _, v in ipairs(env.reader:read_inputs_added(first_epoch.epoch_number)) do +for _, v in ipairs(env.reader:read_inputs_added(second_epoch.epoch_number)) do table.insert(inputs, v.data) end -- Compute honest commitment -- 44 is the initial log2_stride currently configured in the smart contracts. local initial_state, commitment = Machine.root_rollup_commitment(env.template_machine, 44, inputs) -assert(first_epoch.initial_machine_state_hash, initial_state) +assert(second_epoch.initial_machine_state_hash, initial_state) local honest_commitment_builder = CommitmentBuilder:new(env.template_machine, inputs, commitment) local patched_commitment_builder1 = PatchedCommitmentBuilder:new({ { hash = Hash.zero, meta_cycle = 1 << 44 } }, @@ -30,9 +40,9 @@ local patched_commitment_builder1 = PatchedCommitmentBuilder:new({ { hash = Hash local patched_commitment_builder2 = PatchedCommitmentBuilder:new({ { hash = Hash.zero, meta_cycle = 2 << 44 } }, honest_commitment_builder) -local player1 = start_sybil(patched_commitment_builder1, env.template_machine, first_epoch.tournament, +local player1 = start_sybil(patched_commitment_builder1, env.template_machine, second_epoch.tournament, inputs) -local player2 = start_sybil(patched_commitment_builder2, env.template_machine, first_epoch.tournament, +local player2 = start_sybil(patched_commitment_builder2, env.template_machine, second_epoch.tournament, inputs) env.drive_player_until(player1, function(_, log) @@ -50,15 +60,11 @@ env.drive_player_until(player2, function(_, log) return count > 1 end) - --- Spawn Dave node -env.spawn_node() - -- Wait for node to garbage collect lazy claims -env.wait_until_epoch(1) +env.wait_until_epoch(2) -- validate winners -local winner = env.reader:root_tournament_winner(first_epoch.tournament) +local winner = env.reader:root_tournament_winner(second_epoch.tournament) assert(winner.has_winner) assert(winner.commitment == commitment) assert(winner.final == commitment:last()) diff --git a/prt/tests/rollups/test_cases/gc_tournament.lua b/prt/tests/rollups/test_cases/gc_tournament.lua index ad74704ae..b210d3561 100644 --- a/prt/tests/rollups/test_cases/gc_tournament.lua +++ b/prt/tests/rollups/test_cases/gc_tournament.lua @@ -11,18 +11,28 @@ local CommitmentBuilder = require "computation.commitment" -- Main Execution env.spawn_blockchain {env.sample_inputs[1]} local first_epoch = assert(env.reader:read_epochs_sealed()[1]) -assert(first_epoch.input_upper_bound == 1) -- there's one input for epoch 0 already! +assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! +-- Add 3 inputs to epoch 1 +env.sender:tx_add_inputs { env.sample_inputs[1], env.sample_inputs[1], env.sample_inputs[1] } + +-- Spawn Dave node +env.spawn_node() + +-- advance such that epoch 0 is finished +local second_epoch = env.roll_epoch() +assert(second_epoch.epoch_number == 1) +assert(second_epoch.input_upper_bound == 4) -- there are 4 inputs for epoch 1! local inputs = {} -for _, v in ipairs(env.reader:read_inputs_added(first_epoch.epoch_number)) do +for _, v in ipairs(env.reader:read_inputs_added(second_epoch.epoch_number)) do table.insert(inputs, v.data) end -- Compute honest commitment -- 44 is the initial log2_stride currently configured in the smart contracts. local initial_state, commitment = Machine.root_rollup_commitment(env.template_machine, 44, inputs) -assert(first_epoch.initial_machine_state_hash, initial_state) +assert(second_epoch.initial_machine_state_hash, initial_state) local honest_commitment_builder = CommitmentBuilder:new(env.template_machine, inputs, commitment) local patched_commitment_builder1 = PatchedCommitmentBuilder:new({ { hash = Hash.zero, meta_cycle = 1 << 44 } }, @@ -30,9 +40,9 @@ local patched_commitment_builder1 = PatchedCommitmentBuilder:new({ { hash = Hash local patched_commitment_builder2 = PatchedCommitmentBuilder:new({ { hash = Hash.zero, meta_cycle = 2 << 44 } }, honest_commitment_builder) -local player1 = start_sybil(patched_commitment_builder1, env.template_machine, first_epoch.tournament, +local player1 = start_sybil(patched_commitment_builder1, env.template_machine, second_epoch.tournament, inputs) -local player2 = start_sybil(patched_commitment_builder2, env.template_machine, first_epoch.tournament, +local player2 = start_sybil(patched_commitment_builder2, env.template_machine, second_epoch.tournament, inputs) env.drive_player_until(player1, function(_, _) @@ -46,14 +56,11 @@ env.drive_player_until(player1, function(_, _) return false end) --- Spawn Dave node -env.spawn_node() - -- Wait for node to garbage collect lazy claims -env.wait_until_epoch(1) +env.wait_until_epoch(2) -- validate winners -local winner = env.reader:root_tournament_winner(first_epoch.tournament) +local winner = env.reader:root_tournament_winner(second_epoch.tournament) assert(winner.has_winner) assert(winner.commitment == commitment) assert(winner.final == commitment:last()) diff --git a/prt/tests/rollups/test_cases/simple.lua b/prt/tests/rollups/test_cases/simple.lua index bfdb3026f..7f57553cf 100755 --- a/prt/tests/rollups/test_cases/simple.lua +++ b/prt/tests/rollups/test_cases/simple.lua @@ -7,7 +7,7 @@ local env = require "test_env" -- Main Execution env.spawn_blockchain {env.sample_inputs[1]} local first_epoch = assert(env.reader:read_epochs_sealed()[1]) -assert(first_epoch.input_upper_bound == 1) -- there's one input for epoch 0 already! +assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! -- Add 3 inputs to epoch 1 env.sender:tx_add_inputs { env.sample_inputs[1], env.sample_inputs[1], env.sample_inputs[1] } diff --git a/prt/tests/rollups/test_cases/stf_all.lua b/prt/tests/rollups/test_cases/stf_all.lua index d58e033b1..633f28932 100755 --- a/prt/tests/rollups/test_cases/stf_all.lua +++ b/prt/tests/rollups/test_cases/stf_all.lua @@ -7,7 +7,7 @@ local env = require "test_env" -- Main Execution env.spawn_blockchain {env.sample_inputs[1]} local first_epoch = assert(env.reader:read_epochs_sealed()[1]) -assert(first_epoch.input_upper_bound == 1) -- there's one input for epoch 0 already! +assert(first_epoch.input_upper_bound == 0) -- epoch 0 is empty! -- Add 3 inputs to epoch 1 env.sender:tx_add_inputs { env.sample_inputs[1], env.sample_inputs[1], env.sample_inputs[1] } diff --git a/prt/tests/rollups/test_env.lua b/prt/tests/rollups/test_env.lua index e6ca8a853..b34b1dee6 100644 --- a/prt/tests/rollups/test_env.lua +++ b/prt/tests/rollups/test_env.lua @@ -60,8 +60,8 @@ function Env.spawn_blockchain(inputs) Env.app_address = Env.reader.app_address Env.consensus_address = Env.reader.consensus_address Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], blockchain.endpoint) - Env.sender:tx_add_inputs(inputs) Env.sender:tx_new_dave_app(TEMPLATE_MACHINE_HASH, SALT) + Env.sender:tx_add_inputs(inputs) Env.sender:advance_blocks(2) return blockchain end From 19fe91e1568b9f9dd8643b72b8028d6757c57034 Mon Sep 17 00:00:00 2001 From: Zehui Zheng Date: Tue, 13 Jan 2026 17:48:55 -0800 Subject: [PATCH 011/113] feat: bump emulator to 0.20 chore: update emulator submodule, link omp chore: update step submodule and rename fields chore: update bindings, fix tests chore: update CHECKPOINT_ADDRESS --- .github/actions/cartesi-machine/action.yml | 2 +- .github/workflows/build.yml | 8 +- .../contracts/src/DaveConsensus.sol | 2 +- .../contracts/test/DaveAppFactory.t.sol | 2 +- .../node/blockchain-reader/src/lib.rs | 5 +- .../node/blockchain-reader/src/test_utils.rs | 9 +- .../src/persistent_state_access.rs | 5 +- .../node/state-manager/src/rollups_machine.rs | 6 +- .../node/state-manager/src/sql/test_helper.rs | 5 +- justfile | 4 +- machine/emulator | 2 +- machine/rust-bindings/Cargo.toml | 2 +- .../cartesi-machine-sys/build.rs | 24 +- .../cartesi-machine/src/config/machine.rs | 228 +++++++++++++++--- .../cartesi-machine/src/constants.rs | 18 +- .../cartesi-machine/src/machine.rs | 118 +++++---- machine/step | 2 +- prt/client-lua/computation/constants.lua | 2 +- prt/client-lua/computation/machine.lua | 2 +- prt/client-lua/cryptography/hash.lua | 3 +- prt/client-rs/core/src/machine/instance.rs | 13 +- .../CartesiStateTransition.sol | 2 +- .../state-transition/CmioStateTransition.sol | 4 +- prt/measure_constants/Dockerfile | 2 +- .../runners/helpers/patched_commitment.lua | 2 +- prt/tests/rollups/dave/node.lua | 10 +- prt/tests/rollups/justfile | 2 +- prt/tests/rollups/test_cases/simple.lua | 2 +- prt/tests/rollups/test_env.lua | 6 +- test/programs/justfile | 17 +- 30 files changed, 369 insertions(+), 140 deletions(-) diff --git a/.github/actions/cartesi-machine/action.yml b/.github/actions/cartesi-machine/action.yml index 8df8a10ce..bc40b93b9 100644 --- a/.github/actions/cartesi-machine/action.yml +++ b/.github/actions/cartesi-machine/action.yml @@ -4,7 +4,7 @@ inputs: version: description: 'Version of Cartesi Machine to install' required: false - default: 0.19.0 + default: 0.20.0 suffix-version: description: 'Suffix of Cartesi Machine to install' required: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d879e8fdd..ebe9a7389 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - name: Install Cartesi Machine uses: ./.github/actions/cartesi-machine with: - version: 0.19.0 + version: 0.20.0 suffix-version: "" - name: Setup env @@ -71,7 +71,7 @@ jobs: - name: Install Cartesi Machine uses: ./.github/actions/cartesi-machine with: - version: 0.19.0 + version: 0.20.0 - name: Download PRT contracts working-directory: ./prt/contracts run: | @@ -138,8 +138,8 @@ jobs: working-directory: ./machine/emulator run: | make bundle-boost - wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/v0.19.0/add-generated-files.diff - echo "a892e2d9f5c331f5e80bcb5db4133e7db625aa4d14ffdf9467b75c4c34d1744f add-generated-files.diff" | sha256sum -c + wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/v0.20.0/add-generated-files.diff + echo "d9c2afcefc2759e7cd37bbedc83d54c81515f0fddb671103b489b8789aee33bb add-generated-files.diff" | sha256sum -c git apply add-generated-files.diff rm add-generated-files.diff make diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index eabf87f7c..bde7f1ad6 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -242,7 +242,7 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { require(proof.length == Memory.LOG2_MAX_SIZE, InvalidOutputsMerkleRootProofSize(proof.length)); bytes32 allegedStateHash = proof.merkleRootAfterReplacement( - EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, + EmulatorConstants.AR_CMIO_TX_BUFFER_START >> EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE, keccak256(abi.encode(outputsMerkleRoot)), LibKeccak256.hashPair ); diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index ff954c416..b67b71fab 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -167,7 +167,7 @@ contract DaveAppFactoryTest is Test { bytes32[] memory outputsMerkleRootProof = _randomProof(Memory.LOG2_MAX_SIZE); bytes32 machineMerkleRoot = outputsMerkleRootProof.merkleRootAfterReplacement( - EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, + EmulatorConstants.AR_CMIO_TX_BUFFER_START >> EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE, keccak256(abi.encode(outputsMerkleRoot)) ); diff --git a/cartesi-rollups/node/blockchain-reader/src/lib.rs b/cartesi-rollups/node/blockchain-reader/src/lib.rs index ce95bf1cc..e86472bd7 100644 --- a/cartesi-rollups/node/blockchain-reader/src/lib.rs +++ b/cartesi-rollups/node/blockchain-reader/src/lib.rs @@ -572,7 +572,10 @@ mod blockchain_reader_tests { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index fc9c49ce1..9b20bfcea 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -13,7 +13,7 @@ use cartesi_rollups_contracts::i_input_box::IInputBox; use serde::Deserialize; use std::{ fs::{self, File}, - io::Read, + io::{Read, Seek}, path::PathBuf, }; @@ -77,8 +77,11 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let dave_app_factory = deployment_address("DaveAppFactory"); let initial_hash = { - // $ xxd -p -c32 test/programs/echo/machine-image/hash - let mut file = File::open(program_path.join("machine-image").join("hash")).unwrap(); + // Root hash is stored in hash_tree.sht at offset 0x60 (node 1's hash in sparse tree). + // Equivalent to: xxd -seek 0x60 -l 0x20 -c 0x20 -p .../machine-image/hash_tree.sht + let mut file = + File::open(program_path.join("machine-image").join("hash_tree.sht")).unwrap(); + file.seek(std::io::SeekFrom::Start(0x60)).unwrap(); let mut buffer = [0u8; 32]; file.read_exact(&mut buffer).unwrap(); buffer diff --git a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs index b11d772f7..704d1a601 100644 --- a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs +++ b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs @@ -330,7 +330,10 @@ mod tests { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/cartesi-rollups/node/state-manager/src/rollups_machine.rs b/cartesi-rollups/node/state-manager/src/rollups_machine.rs index 8f013741b..8c0a9d5fc 100644 --- a/cartesi-rollups/node/state-manager/src/rollups_machine.rs +++ b/cartesi-rollups/node/state-manager/src/rollups_machine.rs @@ -10,7 +10,7 @@ use cartesi_prt_core::machine::constants::{ use crate::{CommitmentLeaf, Proof}; use cartesi_machine::{ config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::{break_reason, pma::TX_START}, + constants::{break_reason, machine::TREE_LOG2_ROOT_SIZE, pma::TX_START}, error::{MachineError, MachineResult}, machine::Machine, types::{ @@ -45,7 +45,7 @@ pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE); -pub const CHECKPOINT_ADDRESS: u64 = 0x7ffff000; +pub const CHECKPOINT_ADDRESS: u64 = 0xfe0; pub struct RollupsMachine { machine: Machine, @@ -88,7 +88,7 @@ impl RollupsMachine { } pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { - let proof = self.machine.proof(TX_START, 5)?; + let proof = self.machine.proof(TX_START, 5, TREE_LOG2_ROOT_SIZE)?; let siblings = Proof::new(proof.sibling_hashes); let output_merkle = self.machine.read_memory(TX_START, 32)?; diff --git a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs b/cartesi-rollups/node/state-manager/src/sql/test_helper.rs index 73758261e..d849c6b06 100644 --- a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs +++ b/cartesi-rollups/node/state-manager/src/sql/test_helper.rs @@ -21,7 +21,10 @@ pub fn setup_db() -> (TempDir, Connection) { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/justfile b/justfile index 96d84b19a..9cbb28fcb 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ update-submodules: git submodule update --recursive --init -apply-generated-files-diff VERSION="v0.19.0" FILEHASH="a892e2d9f5c331f5e80bcb5db4133e7db625aa4d14ffdf9467b75c4c34d1744f": +apply-generated-files-diff VERSION="v0.20.0" FILEHASH="d9c2afcefc2759e7cd37bbedc83d54c81515f0fddb671103b489b8789aee33bb": cd machine/emulator && \ (wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/{{VERSION}}/add-generated-files.diff && \ (echo "{{FILEHASH}} add-generated-files.diff" | sha256sum -c) && \ @@ -18,7 +18,7 @@ clean-contracts: clean-consensus-contracts clean-prt-contracts clean-bindings cl make -C machine/emulator clean depclean distclean setup: update-submodules clean-emulator clean-contracts bundle-boost apply-generated-files-diff - make -C machine/emulator # Requires docker, necessary for machine bindings + make -C machine/emulator -j8 # Requires docker, necessary for machine bindings # Run this once after cloning, if using a docker environment setup-docker: setup build-docker-image diff --git a/machine/emulator b/machine/emulator index ce402f96d..8bfca6912 160000 --- a/machine/emulator +++ b/machine/emulator @@ -1 +1 @@ -Subproject commit ce402f96d6757c8f4b2f08cba861cd80ab6bf834 +Subproject commit 8bfca6912f4849e03b7b55677e17e385c0b2dfbe diff --git a/machine/rust-bindings/Cargo.toml b/machine/rust-bindings/Cargo.toml index 914001112..57ea66af8 100644 --- a/machine/rust-bindings/Cargo.toml +++ b/machine/rust-bindings/Cargo.toml @@ -8,7 +8,7 @@ members = [ [workspace.package] -version = "0.19.0" +version = "0.20.0" edition = "2021" license = "Apache-2.0" diff --git a/machine/rust-bindings/cartesi-machine-sys/build.rs b/machine/rust-bindings/cartesi-machine-sys/build.rs index 527c6543a..bb922419b 100644 --- a/machine/rust-bindings/cartesi-machine-sys/build.rs +++ b/machine/rust-bindings/cartesi-machine-sys/build.rs @@ -39,6 +39,28 @@ fn main() { } } + // OpenMP linker configuration (cross-platform) + if cfg!(target_os = "macos") { + // macOS: Try Homebrew first, then MacPorts + let homebrew_libomp = PathBuf::from("/opt/homebrew/opt/libomp"); + if homebrew_libomp.exists() { + println!("cargo:rustc-link-search={}/lib", homebrew_libomp.display()); + println!("cargo:rustc-link-lib=omp"); + } else { + let macports_libomp = PathBuf::from("/opt/local/lib/libomp"); + if macports_libomp.exists() { + println!("cargo:rustc-link-search=/opt/local/lib/libomp"); + println!("cargo:rustc-link-lib=gomp"); + } else { + // Fallback: let system linker find it + println!("cargo:rustc-link-lib=omp"); + } + } + } else { + // Linux and other Unix-like systems: libgomp comes with GCC + println!("cargo:rustc-link-lib=gomp"); + } + // // Generate bindings // @@ -197,7 +219,7 @@ mod build_cm { process::{Command, Stdio}, }; - const VERSION_STRING: &str = "v0.19.0"; + const VERSION_STRING: &str = "v0.20.0"; pub fn download(machine_dir_path: &Path) { let patch_file = machine_dir_path.join("add-generated-files.diff"); diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index e599ed6f0..5d3b6dde6 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -4,15 +4,43 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; +/// Backing store config; matches C++ backing_store_config (data_filename, etc.). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct BackingStoreConfig { + #[serde(default)] + pub shared: bool, + #[serde(default)] + pub create: bool, + #[serde(default)] + pub truncate: bool, + #[serde(default)] + pub data_filename: PathBuf, + #[serde(default)] + pub dht_filename: PathBuf, + #[serde(default)] + pub dpt_filename: PathBuf, +} + +/// Config with only backing_store; matches C++ backing_store_config_only. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct BackingStoreConfigOnly { + #[serde(default)] + pub backing_store: BackingStoreConfig, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MachineConfig { pub processor: ProcessorConfig, pub ram: RAMConfig, pub dtb: DTBConfig, pub flash_drive: FlashDriveConfigs, + #[serde(default)] pub tlb: TLBConfig, + #[serde(default)] pub clint: CLINTConfig, + #[serde(default)] pub plic: PLICConfig, + #[serde(default)] pub htif: HTIFConfig, pub uarch: UarchConfig, pub cmio: CmioConfig, @@ -93,106 +121,206 @@ fn default_config() -> MachineConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProcessorConfig { + #[serde(default)] + pub backing_store: BackingStoreConfig, + #[serde(default)] pub x0: u64, + #[serde(default)] pub x1: u64, + #[serde(default)] pub x2: u64, + #[serde(default)] pub x3: u64, + #[serde(default)] pub x4: u64, + #[serde(default)] pub x5: u64, + #[serde(default)] pub x6: u64, + #[serde(default)] pub x7: u64, + #[serde(default)] pub x8: u64, + #[serde(default)] pub x9: u64, + #[serde(default)] pub x10: u64, + #[serde(default)] pub x11: u64, + #[serde(default)] pub x12: u64, + #[serde(default)] pub x13: u64, + #[serde(default)] pub x14: u64, + #[serde(default)] pub x15: u64, + #[serde(default)] pub x16: u64, + #[serde(default)] pub x17: u64, + #[serde(default)] pub x18: u64, + #[serde(default)] pub x19: u64, + #[serde(default)] pub x20: u64, + #[serde(default)] pub x21: u64, + #[serde(default)] pub x22: u64, + #[serde(default)] pub x23: u64, + #[serde(default)] pub x24: u64, + #[serde(default)] pub x25: u64, + #[serde(default)] pub x26: u64, + #[serde(default)] pub x27: u64, + #[serde(default)] pub x28: u64, + #[serde(default)] pub x29: u64, + #[serde(default)] pub x30: u64, + #[serde(default)] pub x31: u64, + #[serde(default)] pub f0: u64, + #[serde(default)] pub f1: u64, + #[serde(default)] pub f2: u64, + #[serde(default)] pub f3: u64, + #[serde(default)] pub f4: u64, + #[serde(default)] pub f5: u64, + #[serde(default)] pub f6: u64, + #[serde(default)] pub f7: u64, + #[serde(default)] pub f8: u64, + #[serde(default)] pub f9: u64, + #[serde(default)] pub f10: u64, + #[serde(default)] pub f11: u64, + #[serde(default)] pub f12: u64, + #[serde(default)] pub f13: u64, + #[serde(default)] pub f14: u64, + #[serde(default)] pub f15: u64, + #[serde(default)] pub f16: u64, + #[serde(default)] pub f17: u64, + #[serde(default)] pub f18: u64, + #[serde(default)] pub f19: u64, + #[serde(default)] pub f20: u64, + #[serde(default)] pub f21: u64, + #[serde(default)] pub f22: u64, + #[serde(default)] pub f23: u64, + #[serde(default)] pub f24: u64, + #[serde(default)] pub f25: u64, + #[serde(default)] pub f26: u64, + #[serde(default)] pub f27: u64, + #[serde(default)] pub f28: u64, + #[serde(default)] pub f29: u64, + #[serde(default)] pub f30: u64, + #[serde(default)] pub f31: u64, + #[serde(default)] pub pc: u64, + #[serde(default)] pub fcsr: u64, + #[serde(default)] pub mvendorid: u64, + #[serde(default)] pub marchid: u64, + #[serde(default)] pub mimpid: u64, + #[serde(default)] pub mcycle: u64, + #[serde(default)] pub icycleinstret: u64, + #[serde(default)] pub mstatus: u64, + #[serde(default)] pub mtvec: u64, + #[serde(default)] pub mscratch: u64, + #[serde(default)] pub mepc: u64, + #[serde(default)] pub mcause: u64, + #[serde(default)] pub mtval: u64, + #[serde(default)] pub misa: u64, + #[serde(default)] pub mie: u64, + #[serde(default)] pub mip: u64, + #[serde(default)] pub medeleg: u64, + #[serde(default)] pub mideleg: u64, + #[serde(default)] pub mcounteren: u64, + #[serde(default)] pub menvcfg: u64, + #[serde(default)] pub stvec: u64, + #[serde(default)] pub sscratch: u64, + #[serde(default)] pub sepc: u64, + #[serde(default)] pub scause: u64, + #[serde(default)] pub stval: u64, + #[serde(default)] pub satp: u64, + #[serde(default)] pub scounteren: u64, + #[serde(default)] pub senvcfg: u64, + #[serde(default)] pub ilrsc: u64, + #[serde(default)] pub iprv: u64, + #[serde(default)] #[serde(rename = "iflags_X")] pub iflags_x: u64, + #[serde(default)] #[serde(rename = "iflags_Y")] pub iflags_y: u64, + #[serde(default)] #[serde(rename = "iflags_H")] pub iflags_h: u64, + #[serde(default)] pub iunrep: u64, } @@ -205,7 +333,7 @@ impl Default for ProcessorConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RAMConfig { pub length: u64, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -213,7 +341,7 @@ pub struct DTBConfig { pub bootargs: String, pub init: String, pub entrypoint: String, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } impl Default for DTBConfig { @@ -224,18 +352,18 @@ impl Default for DTBConfig { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MemoryRangeConfig { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub start: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub length: Option, - pub image_filename: PathBuf, - pub shared: bool, + #[serde(default)] + pub read_only: bool, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct CmioBufferConfig { - pub image_filename: PathBuf, - pub shared: bool, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -272,91 +400,113 @@ pub struct VirtIODeviceConfig { pub type FlashDriveConfigs = Vec; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TLBConfig { - pub image_filename: PathBuf, -} - -impl Default for TLBConfig { - fn default() -> Self { - default_config().tlb - } + #[serde(default)] + pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct CLINTConfig { + #[serde(default)] pub mtimecmp: u64, } -impl Default for CLINTConfig { - fn default() -> Self { - default_config().clint - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct PLICConfig { + #[serde(default)] pub girqpend: u64, + #[serde(default)] pub girqsrvd: u64, } -impl Default for PLICConfig { - fn default() -> Self { - default_config().plic - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct HTIFConfig { + #[serde(default)] pub fromhost: u64, + #[serde(default)] pub tohost: u64, + #[serde(default)] pub console_getchar: bool, + #[serde(default)] pub yield_manual: bool, + #[serde(default)] pub yield_automatic: bool, } -impl Default for HTIFConfig { - fn default() -> Self { - default_config().htif - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UarchProcessorConfig { + #[serde(default)] + pub backing_store: BackingStoreConfig, + #[serde(default)] pub x0: u64, + #[serde(default)] pub x1: u64, + #[serde(default)] pub x2: u64, + #[serde(default)] pub x3: u64, + #[serde(default)] pub x4: u64, + #[serde(default)] pub x5: u64, + #[serde(default)] pub x6: u64, + #[serde(default)] pub x7: u64, + #[serde(default)] pub x8: u64, + #[serde(default)] pub x9: u64, + #[serde(default)] pub x10: u64, + #[serde(default)] pub x11: u64, + #[serde(default)] pub x12: u64, + #[serde(default)] pub x13: u64, + #[serde(default)] pub x14: u64, + #[serde(default)] pub x15: u64, + #[serde(default)] pub x16: u64, + #[serde(default)] pub x17: u64, + #[serde(default)] pub x18: u64, + #[serde(default)] pub x19: u64, + #[serde(default)] pub x20: u64, + #[serde(default)] pub x21: u64, + #[serde(default)] pub x22: u64, + #[serde(default)] pub x23: u64, + #[serde(default)] pub x24: u64, + #[serde(default)] pub x25: u64, + #[serde(default)] pub x26: u64, + #[serde(default)] pub x27: u64, + #[serde(default)] pub x28: u64, + #[serde(default)] pub x29: u64, + #[serde(default)] pub x30: u64, + #[serde(default)] pub x31: u64, + #[serde(default)] pub pc: u64, + #[serde(default)] pub cycle: u64, + #[serde(default)] pub halt_flag: bool, } @@ -368,9 +518,9 @@ impl Default for UarchProcessorConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UarchRAMConfig { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub length: Option, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } impl Default for UarchRAMConfig { diff --git a/machine/rust-bindings/cartesi-machine/src/constants.rs b/machine/rust-bindings/cartesi-machine/src/constants.rs index 9e8088af8..fef0a3752 100644 --- a/machine/rust-bindings/cartesi-machine/src/constants.rs +++ b/machine/rust-bindings/cartesi-machine/src/constants.rs @@ -6,19 +6,19 @@ pub mod machine { use cartesi_machine_sys::*; // pub const CYCLE_MAX: u64 = CM_MCYCLE_MAX as u64; - pub const HASH_SIZE: u32 = CM_HASH_SIZE; - pub const TREE_LOG2_WORD_SIZE: u32 = CM_TREE_LOG2_WORD_SIZE; - pub const TREE_LOG2_PAGE_SIZE: u32 = CM_TREE_LOG2_PAGE_SIZE; - pub const TREE_LOG2_ROOT_SIZE: u32 = CM_TREE_LOG2_ROOT_SIZE; + pub const HASH_SIZE: u32 = CM_HASH_SIZE as u32; + pub const TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; + pub const TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; + pub const TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; } pub mod pma { use cartesi_machine_sys::*; - pub const RX_START: u64 = CM_PMA_CMIO_RX_BUFFER_START as u64; - pub const RX_LOG2_SIZE: u64 = CM_PMA_CMIO_RX_BUFFER_LOG2_SIZE as u64; - pub const TX_START: u64 = CM_PMA_CMIO_TX_BUFFER_START as u64; - pub const TX_LOG2_SIZE: u64 = CM_PMA_CMIO_TX_BUFFER_LOG2_SIZE as u64; - pub const RAM_START: u64 = CM_PMA_RAM_START as u64; + pub const RX_START: u64 = CM_AR_CMIO_RX_BUFFER_START as u64; + pub const RX_LOG2_SIZE: u64 = CM_AR_CMIO_RX_BUFFER_LOG2_SIZE as u64; + pub const TX_START: u64 = CM_AR_CMIO_TX_BUFFER_START as u64; + pub const TX_LOG2_SIZE: u64 = CM_AR_CMIO_TX_BUFFER_LOG2_SIZE as u64; + pub const RAM_START: u64 = CM_AR_RAM_START as u64; } pub mod break_reason { diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 371ccb070..2bc755538 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -92,12 +92,14 @@ impl Machine { pub fn create(config: &MachineConfig, runtime_config: &RuntimeConfig) -> Result { let config_json = serialize_to_json!(&config); let runtime_config_json = serialize_to_json!(&runtime_config); + let dir_cstr = CString::new("").unwrap(); // in-memory machine let mut machine: *mut cartesi_machine_sys::cm_machine = ptr::null_mut(); let err_code = unsafe { cartesi_machine_sys::cm_create_new( config_json.as_ptr(), runtime_config_json.as_ptr(), + dir_cstr.as_ptr(), &mut machine, ) }; @@ -116,6 +118,7 @@ impl Machine { cartesi_machine_sys::cm_load_new( dir_cstr.as_ptr(), runtime_config_json.as_ptr(), + cartesi_machine_sys::CM_SHARING_CONFIG, &mut machine, ) }; @@ -125,9 +128,18 @@ impl Machine { } /// Stores a machine instance to a directory, serializing its entire state. + /// Uses CM_SHARING_ALL so that the current machine state is written for all + /// address ranges (required when storing in-memory machines that have no + /// backing files). pub fn store(&mut self, dir: &Path) -> Result<()> { let dir_cstr = path_to_cstring(dir); - let err_code = unsafe { cartesi_machine_sys::cm_store(self.machine, dir_cstr.as_ptr()) }; + let err_code = unsafe { + cartesi_machine_sys::cm_store( + self.machine, + dir_cstr.as_ptr(), + cartesi_machine_sys::CM_SHARING_ALL, + ) + }; check_err!(err_code)?; Ok(()) @@ -164,25 +176,20 @@ impl Machine { shared: bool, image_path: Option<&Path>, ) -> Result<()> { - let image_cstr = match image_path { - Some(path) => path_to_cstring(path), - None => CString::new("").unwrap(), - }; - - let image_ptr = if image_path.is_some() { - image_cstr.as_ptr() - } else { - ptr::null() - }; + let range_config = serde_json::json!({ + "start": start, + "length": length, + "read_only": false, + "backing_store": { + "data_filename": image_path.map(|p| p.to_string_lossy().to_string()).unwrap_or_default(), + "shared": shared + } + }); + + let range_json = serialize_to_json!(&range_config); let err_code = unsafe { - cartesi_machine_sys::cm_replace_memory_range( - self.machine, - start, - length, - shared, - image_ptr, - ) + cartesi_machine_sys::cm_replace_memory_range(self.machine, range_json.as_ptr()) }; check_err!(err_code)?; @@ -205,7 +212,7 @@ impl Machine { pub fn memory_ranges(&mut self) -> Result { let mut ranges_ptr: *const c_char = ptr::null(); let err_code = - unsafe { cartesi_machine_sys::cm_get_memory_ranges(self.machine, &mut ranges_ptr) }; + unsafe { cartesi_machine_sys::cm_get_address_ranges(self.machine, &mut ranges_ptr) }; check_err!(err_code)?; let ranges = parse_json_from_cstring!(ranges_ptr); @@ -223,13 +230,19 @@ impl Machine { } /// Obtains the proof for a node in the machine state Merkle tree. - pub fn proof(&mut self, address: u64, log2_size: u32) -> Result { + pub fn proof( + &mut self, + address: u64, + log2_target_size: u32, + log2_root_size: u32, + ) -> Result { let mut proof_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_proof( self.machine, address, - log2_size as i32, + log2_target_size as i32, + log2_root_size as ::std::os::raw::c_int, &mut proof_ptr, ) }; @@ -547,7 +560,6 @@ impl Machine { let mut break_reason = BreakReason::default(); let err_code = unsafe { cartesi_machine_sys::cm_verify_step( - ptr::null(), root_hash_before, log_filename_c.as_ptr(), mcycle_count, @@ -650,7 +662,7 @@ mod tests { use crate::{ config::{ - machine::{DTBConfig, MachineConfig, MemoryRangeConfig, RAMConfig}, + machine::{BackingStoreConfig, MachineConfig, MemoryRangeConfig, RAMConfig}, runtime::RuntimeConfig, }, constants, @@ -659,36 +671,46 @@ mod tests { }; fn make_basic_machine_config() -> MachineConfig { - MachineConfig::new_with_ram(RAMConfig { + let mut config = Machine::default_config().expect("failed to get default config"); + config.ram = RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), - }) - .dtb(DTBConfig { - entrypoint: "echo Hello from inside!".to_string(), - ..Default::default() - }) - .add_flash_drive(MemoryRangeConfig { - image_filename: "../../../test/programs/rootfs.ext2".into(), + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, + }; + config.dtb.entrypoint = "echo Hello from inside!".to_string(); + config.flash_drive = vec![MemoryRangeConfig { + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/rootfs.ext2".into(), + ..Default::default() + }, ..Default::default() - }) + }]; + config } fn make_cmio_machine_config() -> MachineConfig { - MachineConfig::new_with_ram(RAMConfig { + let mut config = Machine::default_config().expect("failed to get default config"); + config.ram = RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), - }) - .dtb(DTBConfig { - entrypoint: - "echo '{\"domain\":16,\"id\":\"'$(echo -n Hello from inside! | hex --encode)'\"}' \ + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, + }; + config.dtb.entrypoint = + "echo '{\"domain\":16,\"id\":\"'$(echo -n Hello from inside! | hex --encode)'\"}' \ | rollup gio | grep -Eo '0x[0-9a-f]+' | tr -d '\\n' | hex --decode; echo" - .to_string(), + .to_string(); + config.flash_drive = vec![MemoryRangeConfig { + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/rootfs.ext2".into(), + ..Default::default() + }, ..Default::default() - }) - .add_flash_drive(MemoryRangeConfig { - image_filename: "../../../test/programs/rootfs.ext2".into(), - ..Default::default() - }) + }]; + config } fn create_machine(config: &MachineConfig) -> Result { @@ -918,7 +940,11 @@ mod tests { assert!(mem.iter().all(|x| *x == 1)); let log2_size = u64::BITS - range.length.leading_zeros(); - let proof: Proof = machine.proof(range.start, u64::BITS - range.length.leading_zeros())?; + let proof: Proof = machine.proof( + range.start, + u64::BITS - range.length.leading_zeros(), + constants::machine::TREE_LOG2_ROOT_SIZE, + )?; assert_eq!(proof.target_address, range.start); assert_eq!(proof.log2_target_size, log2_size as u64); diff --git a/machine/step b/machine/step index e8050ddfd..3f5d163df 160000 --- a/machine/step +++ b/machine/step @@ -1 +1 @@ -Subproject commit e8050ddfdb986d6014bebdfc1d33e16cb3b2528a +Subproject commit 3f5d163df0f7564fef3345fc919252a371e5fb9f diff --git a/prt/client-lua/computation/constants.lua b/prt/client-lua/computation/constants.lua index 1a7c9f44a..add83f6aa 100644 --- a/prt/client-lua/computation/constants.lua +++ b/prt/client-lua/computation/constants.lua @@ -12,7 +12,7 @@ local log2_uarch_span_to_input = log2_uarch_span_to_barch + log2_barch_span_to_i local log2_uarch_span_to_epoch = log2_input_span_to_epoch + log2_barch_span_to_input + log2_uarch_span_to_barch -- Checkpoint address for machine state snapshots -local CHECKPOINT_ADDRESS = 0x7ffff000 +local CHECKPOINT_ADDRESS = 0xfe0 local constants = { log2_uarch_span_to_barch = log2_uarch_span_to_barch, diff --git a/prt/client-lua/computation/machine.lua b/prt/client-lua/computation/machine.lua index e292f87e8..655d883aa 100644 --- a/prt/client-lua/computation/machine.lua +++ b/prt/client-lua/computation/machine.lua @@ -334,7 +334,7 @@ function Machine:prove_read_leaf(address) return data end -local keccak = require "cartesi".keccak +local keccak = cartesi.keccak256 function Machine:prove_write_leaf(address) -- always write aligned 32 bytes (one leaf) diff --git a/prt/client-lua/cryptography/hash.lua b/prt/client-lua/cryptography/hash.lua index 30638be47..e334a9a99 100644 --- a/prt/client-lua/cryptography/hash.lua +++ b/prt/client-lua/cryptography/hash.lua @@ -1,4 +1,5 @@ -local keccak = require "cartesi".keccak +local cartesi = require "cartesi" +local keccak = cartesi.keccak256 local conversion = require "utils.conversion" local interned_hashes = {} diff --git a/prt/client-rs/core/src/machine/instance.rs b/prt/client-rs/core/src/machine/instance.rs index 74801ed6f..71b4b5b59 100644 --- a/prt/client-rs/core/src/machine/instance.rs +++ b/prt/client-rs/core/src/machine/instance.rs @@ -9,6 +9,7 @@ use cartesi_dave_merkle::Digest; use cartesi_machine::{ cartesi_machine_sys, config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, + constants::machine::TREE_LOG2_ROOT_SIZE, machine::Machine, types::access_proof::AccessLog, types::{LogType, cmio::CmioResponseReason}, @@ -63,7 +64,7 @@ pub struct MachineInstance { pub snapshot_path: PathBuf, } -const CHECKPOINT_ADDRESS: u64 = 0x7ffff000; +const CHECKPOINT_ADDRESS: u64 = 0xfe0; impl MachineInstance { pub fn new_from_path(path: &str) -> Result { let runtime_config = RuntimeConfig { @@ -332,7 +333,9 @@ impl MachineInstance { // always read aligned 32 bytes (one leaf) let aligned_address = address & !0x1Fu64; let mut read = self.machine.read_memory(aligned_address, 32)?; - let proof = self.machine.proof(aligned_address, 5)?; + let proof = self + .machine + .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -350,7 +353,9 @@ impl MachineInstance { let aligned_address = address & !0x1Fu64; let mut read = self.machine.read_memory(aligned_address, 32)?; let read_hash = Digest::from_data(&read); - let proof = self.machine.proof(aligned_address, 5)?; + let proof = self + .machine + .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -370,7 +375,7 @@ impl MachineInstance { let read = self.machine.read_memory(address, 32)?; let read_hash = Digest::from_data(&read); // Get proof of write address - let proof = self.machine.proof(address, 5)?; + let proof = self.machine.proof(address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); diff --git a/prt/contracts/src/state-transition/CartesiStateTransition.sol b/prt/contracts/src/state-transition/CartesiStateTransition.sol index eb6147bbd..c3f2b7130 100644 --- a/prt/contracts/src/state-transition/CartesiStateTransition.sol +++ b/prt/contracts/src/state-transition/CartesiStateTransition.sol @@ -111,7 +111,7 @@ contract CartesiStateTransition is IStateTransition { // * step // * reset // * advanceStatus - // * getCheckpointHash (only if needed) + // * getRevertRootHash (only if needed) AccessLogs.Context memory accessLogs = AccessLogs.Context(machineState, Buffer.Context(proofs, 0)); diff --git a/prt/contracts/src/state-transition/CmioStateTransition.sol b/prt/contracts/src/state-transition/CmioStateTransition.sol index a43cebc9d..ff11277ae 100644 --- a/prt/contracts/src/state-transition/CmioStateTransition.sol +++ b/prt/contracts/src/state-transition/CmioStateTransition.sol @@ -31,7 +31,7 @@ contract CmioStateTransition is ICmioStateTransition { pure returns (AccessLogs.Context memory) { - a.setCheckpointHash(checkpointState); + a.setRevertRootHash(checkpointState); return a; } @@ -41,7 +41,7 @@ contract CmioStateTransition is ICmioStateTransition { returns (AccessLogs.Context memory) { if (a.advanceStatus() == AdvanceStatus.Status.REJECTED) { - bytes32 checkpointState = a.getCheckpointHash(); + bytes32 checkpointState = a.getRevertRootHash(); a.currentRootHash = checkpointState; } diff --git a/prt/measure_constants/Dockerfile b/prt/measure_constants/Dockerfile index 09eace3f0..6f3364b49 100644 --- a/prt/measure_constants/Dockerfile +++ b/prt/measure_constants/Dockerfile @@ -1,4 +1,4 @@ -FROM cartesi/machine-emulator:0.19.0 +FROM cartesi/machine-emulator:0.20.0 USER root RUN apt-get update && \ diff --git a/prt/tests/common/runners/helpers/patched_commitment.lua b/prt/tests/common/runners/helpers/patched_commitment.lua index 81820f402..a46bf4aed 100644 --- a/prt/tests/common/runners/helpers/patched_commitment.lua +++ b/prt/tests/common/runners/helpers/patched_commitment.lua @@ -32,7 +32,7 @@ local function filter_map_patches(patches, base_cycle, log2_stride, log2_stride_ local span = bint256.one() << (log2_stride_count + log2_stride) local mask = (bint256.one() << log2_stride) - 1 if (patch.meta_cycle & mask):iszero() and -- alignment; first bits are zero - patch.meta_cycle > base_cycle and -- meta_cycle is within lower bound + patch.meta_cycle > base_cycle and -- meta_cycle is within lower bound patch.meta_cycle <= base_cycle + span -- meta_cycle is within upper bounds then local position = ((patch.meta_cycle - base_cycle) >> log2_stride) - 1 diff --git a/prt/tests/rollups/dave/node.lua b/prt/tests/rollups/dave/node.lua index 7ff56ae7e..b1d828e1d 100644 --- a/prt/tests/rollups/dave/node.lua +++ b/prt/tests/rollups/dave/node.lua @@ -61,6 +61,7 @@ sqlite3 -readonly ./_state/%d/db \ 'SELECT repetitions, HEX(leaf) FROM leafs WHERE level=0 ORDER BY leaf_index ASC' 2>&1 ]] function Dave:root_commitment(epoch_index) + print(string.format("[Dave] root_commitment(epoch_index=%d) called", epoch_index)) local query = function() assert(db_exists(epoch_index), string.format("db %d doesn't exist ", epoch_index)) @@ -87,14 +88,21 @@ function Dave:root_commitment(epoch_index) builder:add(leaf, repetitions) end - return initial_state, builder:build(initial_state.root_hash) + local commitment = builder:build(initial_state.root_hash) + print(string.format("[Dave] root_commitment(epoch_index=%d) -> root=%s", epoch_index, commitment.root_hash:hex_string())) + return initial_state, commitment end local initial_state, commitment + local attempt = 0 time.sleep_until(function() + attempt = attempt + 1 self.sender:advance_blocks(1) local ok ok, initial_state, commitment = pcall(query) + if not ok and (attempt == 1 or attempt % 10 == 0) then + print(string.format("[Dave] root_commitment(epoch_index=%d) attempt %d failed: %s", epoch_index, attempt, tostring(initial_state))) + end return ok end, 5) diff --git a/prt/tests/rollups/justfile b/prt/tests/rollups/justfile index 260ed98cc..5aa45d935 100644 --- a/prt/tests/rollups/justfile +++ b/prt/tests/rollups/justfile @@ -10,7 +10,7 @@ test PROGRAM SCRIPT: ANVIL_LOAD_PATH=`realpath {{ANVIL_LOAD_PATH}}` \ ANVIL_DUMP_PATH="anvil_{{PROGRAM}}_{{SCRIPT}}.json" \ TEMPLATE_MACHINE=`realpath ../../../test/programs/{{PROGRAM}}/machine-image` \ - TEMPLATE_MACHINE_HASH=0x`xxd -p -c32 ../../../test/programs/{{PROGRAM}}/machine-image/hash` \ + TEMPLATE_MACHINE_HASH=0x`xxd -seek 0x60 -l 0x20 -c 0x20 -p ../../../test/programs/{{PROGRAM}}/machine-image/hash_tree.sht` \ DAVE_APP_FACTORY=`jq -r .address {{DEPLOYMENTS_DIR}}/DaveAppFactory.json` \ INPUT_BOX=`jq -r .address {{DEPLOYMENTS_DIR}}/InputBox.json` \ ERC20_PORTAL=`jq -r .address {{DEPLOYMENTS_DIR}}/ERC20Portal.json` \ diff --git a/prt/tests/rollups/test_cases/simple.lua b/prt/tests/rollups/test_cases/simple.lua index 7f57553cf..3c790697b 100755 --- a/prt/tests/rollups/test_cases/simple.lua +++ b/prt/tests/rollups/test_cases/simple.lua @@ -5,7 +5,7 @@ local env = require "test_env" -- Main Execution -env.spawn_blockchain {env.sample_inputs[1]} +env.spawn_blockchain { env.sample_inputs[1] } local first_epoch = assert(env.reader:read_epochs_sealed()[1]) assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! diff --git a/prt/tests/rollups/test_env.lua b/prt/tests/rollups/test_env.lua index b34b1dee6..a6d6a2fd7 100644 --- a/prt/tests/rollups/test_env.lua +++ b/prt/tests/rollups/test_env.lua @@ -56,10 +56,12 @@ function Env.spawn_blockchain(inputs) local blockchain = Blockchain:new(ANVIL_LOAD_PATH, ANVIL_DUMP_PATH) Env.blockchain = blockchain - Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, SALT, blockchain.endpoint) + Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, SALT, blockchain + .endpoint) Env.app_address = Env.reader.app_address Env.consensus_address = Env.reader.consensus_address - Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], blockchain.endpoint) + Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], + blockchain.endpoint) Env.sender:tx_new_dave_app(TEMPLATE_MACHINE_HASH, SALT) Env.sender:tx_add_inputs(inputs) Env.sender:advance_blocks(2) diff --git a/test/programs/justfile b/test/programs/justfile index c5acdb2dc..6f30ba3aa 100644 --- a/test/programs/justfile +++ b/test/programs/justfile @@ -1,7 +1,7 @@ download-deps: clean-deps wget https://github.com/cartesi/image-kernel/releases/download/v0.20.0/linux-6.5.13-ctsi-1-v0.20.0.bin \ -O ./linux.bin - wget https://github.com/cartesi/machine-emulator-tools/releases/download/v0.17.1/rootfs-tools.ext2 \ + wget https://github.com/cartesi/machine-emulator-tools/releases/download/v0.17.2/rootfs-tools.ext2 \ -O ./rootfs.ext2 clean-deps: @@ -16,16 +16,16 @@ clean-program prog: # yield build-yield: clean-yield - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./yield/machine-image \ -- "while true; do yield manual rx-accepted; yield manual rx-rejected; done" clean-yield: (clean-program "yield") # echo build-echo: clean-echo - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./echo/machine-image \ -- "ioctl-echo-loop --vouchers=1 --notices=1 --reports=1 --verbose=1 --reject=2" clean-echo: (clean-program "echo") @@ -34,6 +34,9 @@ clean-echo: (clean-program "echo") build-honeypot-snapshot: clean-honeypot-snapshot clean-honeypot-project git clone https://github.com/cartesi/honeypot.git honeypot/project git -C honeypot/project reset --hard 34d00721a527eeb7ed8cce2a13a142e3d8de9aad # v3.0.0 + sed -i 's/,filename:/,data_filename:/g' honeypot/project/Makefile + sed -i 's/--append-bootargs=ro/--append-bootargs=rw/g' honeypot/project/Makefile + sed -i 's/label:state,length:4096,user:dapp/label:state,length:4096,user:dapp,mke2fs:false,mount:false/g' honeypot/project/Makefile mkdir -p honeypot/project/config/devnet ./honeypot/generate-devnet-honeypot-config.sh > honeypot/project/config/devnet/honeypot-config.hpp make -C honeypot/project snapshot HONEYPOT_CONFIG=devnet @@ -44,8 +47,8 @@ clean-honeypot-project: # compute build-compute: clean-compute - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./compute/machine-image \ --max-mcycle=0 \ -- "while dd if=/dev/zero bs=1M count=64 2>/dev/null | md5sum >/dev/null; do :; done" From f82fafc3488b314e79ca658d25ddd32da76b3376 Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Wed, 22 Apr 2026 15:54:36 -0300 Subject: [PATCH 012/113] fix: update to newest machine json schema --- .../cartesi-machine/src/config/machine.rs | 659 +++++++++--------- .../cartesi-machine/src/machine.rs | 16 +- 2 files changed, 340 insertions(+), 335 deletions(-) diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index 5d3b6dde6..5fb933025 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -1,342 +1,239 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +//! Rust mirror of `cartesi::machine_config` and friends from the v0.20 +//! cartesi-machine C++ API. The field names and nesting match the JSON +//! emitted by `cm_get_default_config` / `cm_get_initial_config` and consumed +//! by `cm_create_new`. +//! +//! Invariants this module tries to enforce: +//! +//! 1. **Exact shape match with the C++ side.** Every struct carries +//! `#[serde(deny_unknown_fields)]` so that a future emulator release that +//! adds or renames a field causes a *loud* deserialization failure rather +//! than silent data loss. +//! 2. **No speculative `#[serde(default)]`.** The C++ `to_json` functions +//! always emit every field, so missing fields on deserialize indicate a +//! schema break, not a tolerable omission. Defaults are only applied on +//! types the user *constructs* from scratch (via `Default::default()`), +//! not on fields that participate in JSON round-trips. +//! 3. **Round-trip equality.** `serde_json::from_str::(raw) -> +//! serde_json::to_value` must equal `serde_json::from_str::(raw)` +//! for any JSON produced by the C library. Verified by +//! `test_default_config_json_roundtrip`. + use serde::{Deserialize, Serialize}; use std::path::PathBuf; -/// Backing store config; matches C++ backing_store_config (data_filename, etc.). -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Backing store +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::backing_store_config`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct BackingStoreConfig { - #[serde(default)] pub shared: bool, - #[serde(default)] pub create: bool, - #[serde(default)] pub truncate: bool, - #[serde(default)] pub data_filename: PathBuf, - #[serde(default)] pub dht_filename: PathBuf, - #[serde(default)] pub dpt_filename: PathBuf, } -/// Config with only backing_store; matches C++ backing_store_config_only. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::backing_store_config_only`. Used for memory +/// regions that have no extra per-range config (pmas, uarch ram, cmio rx/tx). +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct BackingStoreConfigOnly { - #[serde(default)] pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct MachineConfig { - pub processor: ProcessorConfig, - pub ram: RAMConfig, - pub dtb: DTBConfig, - pub flash_drive: FlashDriveConfigs, - #[serde(default)] - pub tlb: TLBConfig, - #[serde(default)] - pub clint: CLINTConfig, - #[serde(default)] - pub plic: PLICConfig, - #[serde(default)] - pub htif: HTIFConfig, - pub uarch: UarchConfig, - pub cmio: CmioConfig, - pub virtio: VirtIOConfigs, +// --------------------------------------------------------------------------- +// Register substructures (all nested inside RegistersConfig) +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::iflags_state`. The field names in JSON are the +/// uppercase single letters `X`, `Y`, `H`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct IFlagsConfig { + #[serde(rename = "X")] + pub x: u64, + #[serde(rename = "Y")] + pub y: u64, + #[serde(rename = "H")] + pub h: u64, } -impl MachineConfig { - pub fn new_with_ram(ram: RAMConfig) -> Self { - Self { - processor: ProcessorConfig::default(), - ram, - dtb: DTBConfig::default(), - flash_drive: FlashDriveConfigs::default(), - tlb: TLBConfig::default(), - clint: CLINTConfig::default(), - plic: PLICConfig::default(), - htif: HTIFConfig::default(), - uarch: UarchConfig::default(), - cmio: CmioConfig::default(), - virtio: VirtIOConfigs::default(), - } - } - - pub fn processor(mut self, processor: ProcessorConfig) -> Self { - self.processor = processor; - self - } - - pub fn dtb(mut self, dtb: DTBConfig) -> Self { - self.dtb = dtb; - self - } - - pub fn add_flash_drive(mut self, flash_drive: MemoryRangeConfig) -> Self { - self.flash_drive.push(flash_drive); - self - } - - pub fn tlb(mut self, tlb: TLBConfig) -> Self { - self.tlb = tlb; - self - } - - pub fn clint(mut self, clint: CLINTConfig) -> Self { - self.clint = clint; - self - } - - pub fn plic(mut self, plic: PLICConfig) -> Self { - self.plic = plic; - self - } - - pub fn htif(mut self, htif: HTIFConfig) -> Self { - self.htif = htif; - self - } - - pub fn uarch(mut self, uarch: UarchConfig) -> Self { - self.uarch = uarch; - self - } - - pub fn cmio(mut self, cmio: CmioConfig) -> Self { - self.cmio = cmio; - self - } +/// Mirror of C++ `cartesi::clint_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CLINTConfig { + pub mtimecmp: u64, +} - pub fn add_virtio(mut self, virtio_config: VirtIODeviceConfig) -> Self { - self.virtio.push(virtio_config); - self - } +/// Mirror of C++ `cartesi::plic_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct PLICConfig { + pub girqpend: u64, + pub girqsrvd: u64, } -fn default_config() -> MachineConfig { - crate::machine::Machine::default_config().expect("failed to get default config") +/// Mirror of C++ `cartesi::htif_state`. These are the five HTIF CSRs; the +/// old Rust binding used a different HTIF-runtime struct with feature flags +/// (`console_getchar`, `yield_manual`, `yield_automatic`), which belong to +/// `RuntimeConfig`, not `MachineConfig`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct HTIFConfig { + pub fromhost: u64, + pub tohost: u64, + pub ihalt: u64, + pub iconsole: u64, + pub iyield: u64, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ProcessorConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, - #[serde(default)] +/// Mirror of C++ `cartesi::registers_state`. This is the object emitted at +/// `processor.registers` in the config JSON. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct RegistersConfig { pub x0: u64, - #[serde(default)] pub x1: u64, - #[serde(default)] pub x2: u64, - #[serde(default)] pub x3: u64, - #[serde(default)] pub x4: u64, - #[serde(default)] pub x5: u64, - #[serde(default)] pub x6: u64, - #[serde(default)] pub x7: u64, - #[serde(default)] pub x8: u64, - #[serde(default)] pub x9: u64, - #[serde(default)] pub x10: u64, - #[serde(default)] pub x11: u64, - #[serde(default)] pub x12: u64, - #[serde(default)] pub x13: u64, - #[serde(default)] pub x14: u64, - #[serde(default)] pub x15: u64, - #[serde(default)] pub x16: u64, - #[serde(default)] pub x17: u64, - #[serde(default)] pub x18: u64, - #[serde(default)] pub x19: u64, - #[serde(default)] pub x20: u64, - #[serde(default)] pub x21: u64, - #[serde(default)] pub x22: u64, - #[serde(default)] pub x23: u64, - #[serde(default)] pub x24: u64, - #[serde(default)] pub x25: u64, - #[serde(default)] pub x26: u64, - #[serde(default)] pub x27: u64, - #[serde(default)] pub x28: u64, - #[serde(default)] pub x29: u64, - #[serde(default)] pub x30: u64, - #[serde(default)] pub x31: u64, - #[serde(default)] pub f0: u64, - #[serde(default)] pub f1: u64, - #[serde(default)] pub f2: u64, - #[serde(default)] pub f3: u64, - #[serde(default)] pub f4: u64, - #[serde(default)] pub f5: u64, - #[serde(default)] pub f6: u64, - #[serde(default)] pub f7: u64, - #[serde(default)] pub f8: u64, - #[serde(default)] pub f9: u64, - #[serde(default)] pub f10: u64, - #[serde(default)] pub f11: u64, - #[serde(default)] pub f12: u64, - #[serde(default)] pub f13: u64, - #[serde(default)] pub f14: u64, - #[serde(default)] pub f15: u64, - #[serde(default)] pub f16: u64, - #[serde(default)] pub f17: u64, - #[serde(default)] pub f18: u64, - #[serde(default)] pub f19: u64, - #[serde(default)] pub f20: u64, - #[serde(default)] pub f21: u64, - #[serde(default)] pub f22: u64, - #[serde(default)] pub f23: u64, - #[serde(default)] pub f24: u64, - #[serde(default)] pub f25: u64, - #[serde(default)] pub f26: u64, - #[serde(default)] pub f27: u64, - #[serde(default)] pub f28: u64, - #[serde(default)] pub f29: u64, - #[serde(default)] pub f30: u64, - #[serde(default)] pub f31: u64, - #[serde(default)] pub pc: u64, - #[serde(default)] pub fcsr: u64, - #[serde(default)] pub mvendorid: u64, - #[serde(default)] pub marchid: u64, - #[serde(default)] pub mimpid: u64, - #[serde(default)] pub mcycle: u64, - #[serde(default)] pub icycleinstret: u64, - #[serde(default)] pub mstatus: u64, - #[serde(default)] pub mtvec: u64, - #[serde(default)] pub mscratch: u64, - #[serde(default)] pub mepc: u64, - #[serde(default)] pub mcause: u64, - #[serde(default)] pub mtval: u64, - #[serde(default)] pub misa: u64, - #[serde(default)] pub mie: u64, - #[serde(default)] pub mip: u64, - #[serde(default)] pub medeleg: u64, - #[serde(default)] pub mideleg: u64, - #[serde(default)] pub mcounteren: u64, - #[serde(default)] pub menvcfg: u64, - #[serde(default)] pub stvec: u64, - #[serde(default)] pub sscratch: u64, - #[serde(default)] pub sepc: u64, - #[serde(default)] pub scause: u64, - #[serde(default)] pub stval: u64, - #[serde(default)] pub satp: u64, - #[serde(default)] pub scounteren: u64, - #[serde(default)] pub senvcfg: u64, - #[serde(default)] pub ilrsc: u64, - #[serde(default)] pub iprv: u64, - #[serde(default)] - #[serde(rename = "iflags_X")] - pub iflags_x: u64, - #[serde(default)] - #[serde(rename = "iflags_Y")] - pub iflags_y: u64, - #[serde(default)] - #[serde(rename = "iflags_H")] - pub iflags_h: u64, - #[serde(default)] + pub iflags: IFlagsConfig, pub iunrep: u64, + pub clint: CLINTConfig, + pub plic: PLICConfig, + pub htif: HTIFConfig, +} + +// --------------------------------------------------------------------------- +// Processor +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::processor_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ProcessorConfig { + pub registers: RegistersConfig, + pub backing_store: BackingStoreConfig, } impl Default for ProcessorConfig { fn default() -> Self { - default_config().processor + library_default().processor } } -#[derive(Clone, Debug, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// RAM and DTB +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::ram_config`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct RAMConfig { pub length: u64, pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::dtb_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct DTBConfig { pub bootargs: String, pub init: String, @@ -346,190 +243,174 @@ pub struct DTBConfig { impl Default for DTBConfig { fn default() -> Self { - default_config().dtb + library_default().dtb } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Memory range / flash drive +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::memory_range_config`. The C++ side uses the +/// sentinel `UINT64_MAX` for "auto-detect" on `start`/`length`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct MemoryRangeConfig { - #[serde(skip_serializing_if = "Option::is_none", default)] - pub start: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub length: Option, - #[serde(default)] + pub start: u64, + pub length: u64, pub read_only: bool, pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct CmioBufferConfig { - pub backing_store: BackingStoreConfig, +impl Default for MemoryRangeConfig { + /// Defaults match the C++ `memory_range_config` in-struct initializers: + /// `start` and `length` are `UINT64_MAX` to mean "auto-detect". + fn default() -> Self { + Self { + start: u64::MAX, + length: u64::MAX, + read_only: false, + backing_store: BackingStoreConfig::default(), + } + } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct VirtIOHostfwd { +pub type FlashDriveConfigs = Vec; + +// --------------------------------------------------------------------------- +// CMIO +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::cmio_config`. Both buffers are +/// `backing_store_config_only`. +pub type CmioBufferConfig = BackingStoreConfigOnly; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CmioConfig { + pub rx_buffer: CmioBufferConfig, + pub tx_buffer: CmioBufferConfig, +} + +// --------------------------------------------------------------------------- +// VirtIO +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::virtio_hostfwd_config`. Note that `host_port` +/// and `guest_port` are `uint16_t` on the C++ side. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct VirtIOHostfwdConfig { pub is_udp: bool, pub host_ip: u64, pub guest_ip: u64, - pub host_port: u64, - pub guest_port: u64, + pub host_port: u16, + pub guest_port: u16, } -pub type VirtIOHostfwdArray = Vec; +pub type VirtIOHostfwdArray = Vec; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum VirtIODeviceType { - #[default] +/// Mirror of C++ `cartesi::virtio_device_config` (a `std::variant`). The +/// JSON representation uses `"type"` as the discriminator, matching the +/// `to_json(virtio_device_config)` implementation. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "type", rename_all = "kebab-case", deny_unknown_fields)] +pub enum VirtIODeviceConfig { Console, - P9fs, + P9fs { + tag: String, + host_directory: String, + }, #[serde(rename = "net-user")] - NetUser, + NetUser { hostfwd: VirtIOHostfwdArray }, #[serde(rename = "net-tuntap")] - NetTuntap, + NetTuntap { iface: String }, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct VirtIODeviceConfig { - pub r#type: VirtIODeviceType, - pub tag: String, - pub host_directory: String, - pub hostfwd: VirtIOHostfwdArray, - pub iface: String, +impl Default for VirtIODeviceConfig { + fn default() -> Self { + VirtIODeviceConfig::Console + } } -pub type FlashDriveConfigs = Vec; - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct TLBConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, -} +pub type VirtIOConfigs = Vec; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct CLINTConfig { - #[serde(default)] - pub mtimecmp: u64, -} +// --------------------------------------------------------------------------- +// PMAS +// --------------------------------------------------------------------------- -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct PLICConfig { - #[serde(default)] - pub girqpend: u64, - #[serde(default)] - pub girqsrvd: u64, -} +/// Mirror of C++ `cartesi::pmas_config` (alias for `backing_store_config_only`). +pub type PmasConfig = BackingStoreConfigOnly; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct HTIFConfig { - #[serde(default)] - pub fromhost: u64, - #[serde(default)] - pub tohost: u64, - #[serde(default)] - pub console_getchar: bool, - #[serde(default)] - pub yield_manual: bool, - #[serde(default)] - pub yield_automatic: bool, -} +// --------------------------------------------------------------------------- +// Uarch +// --------------------------------------------------------------------------- -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UarchProcessorConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, - #[serde(default)] +/// Mirror of C++ `cartesi::uarch_registers_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct UarchRegistersConfig { pub x0: u64, - #[serde(default)] pub x1: u64, - #[serde(default)] pub x2: u64, - #[serde(default)] pub x3: u64, - #[serde(default)] pub x4: u64, - #[serde(default)] pub x5: u64, - #[serde(default)] pub x6: u64, - #[serde(default)] pub x7: u64, - #[serde(default)] pub x8: u64, - #[serde(default)] pub x9: u64, - #[serde(default)] pub x10: u64, - #[serde(default)] pub x11: u64, - #[serde(default)] pub x12: u64, - #[serde(default)] pub x13: u64, - #[serde(default)] pub x14: u64, - #[serde(default)] pub x15: u64, - #[serde(default)] pub x16: u64, - #[serde(default)] pub x17: u64, - #[serde(default)] pub x18: u64, - #[serde(default)] pub x19: u64, - #[serde(default)] pub x20: u64, - #[serde(default)] pub x21: u64, - #[serde(default)] pub x22: u64, - #[serde(default)] pub x23: u64, - #[serde(default)] pub x24: u64, - #[serde(default)] pub x25: u64, - #[serde(default)] pub x26: u64, - #[serde(default)] pub x27: u64, - #[serde(default)] pub x28: u64, - #[serde(default)] pub x29: u64, - #[serde(default)] pub x30: u64, - #[serde(default)] pub x31: u64, - #[serde(default)] pub pc: u64, - #[serde(default)] pub cycle: u64, - #[serde(default)] - pub halt_flag: bool, + /// `uint64_t` on the C++ side (shadow-uarch-state.h), not a C++ `bool`. + /// Used as a boolean flag (0 = not halted, non-zero = halted), but the + /// wire representation is an integer. + pub halt_flag: u64, } -impl Default for UarchProcessorConfig { - fn default() -> Self { - default_config().uarch.processor - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UarchRAMConfig { - #[serde(skip_serializing_if = "Option::is_none", default)] - pub length: Option, +/// Mirror of C++ `cartesi::uarch_processor_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct UarchProcessorConfig { + pub registers: UarchRegistersConfig, pub backing_store: BackingStoreConfig, } -impl Default for UarchRAMConfig { +impl Default for UarchProcessorConfig { fn default() -> Self { - default_config().uarch.ram + library_default().uarch.processor } } -#[derive(Clone, Debug, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::uarch_ram_config` (alias for +/// `backing_store_config_only`). +pub type UarchRAMConfig = BackingStoreConfigOnly; + +/// Mirror of C++ `cartesi::uarch_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct UarchConfig { pub processor: UarchProcessorConfig, pub ram: UarchRAMConfig, @@ -537,40 +418,156 @@ pub struct UarchConfig { impl Default for UarchConfig { fn default() -> Self { - default_config().uarch + library_default().uarch } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CmioConfig { - pub rx_buffer: CmioBufferConfig, - pub tx_buffer: CmioBufferConfig, +// --------------------------------------------------------------------------- +// Hash tree +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::hash_function_type`. Serialized as a lower-case +/// string (`"keccak256"` / `"sha256"`). +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum HashFunctionType { + #[default] + Keccak256, + Sha256, +} + +/// Mirror of C++ `cartesi::hash_tree_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct HashTreeConfig { + pub shared: bool, + pub create: bool, + pub sht_filename: PathBuf, + pub phtc_filename: PathBuf, + pub phtc_size: u64, + pub hash_function: HashFunctionType, } -impl Default for CmioConfig { +impl Default for HashTreeConfig { fn default() -> Self { - default_config().cmio + library_default().hash_tree } } -pub type VirtIOConfigs = Vec; +// --------------------------------------------------------------------------- +// Top-level machine config +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::machine_config`. The field ordering matches the +/// order in which `to_json(machine_config)` emits them on the C++ side. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct MachineConfig { + pub processor: ProcessorConfig, + pub ram: RAMConfig, + pub dtb: DTBConfig, + pub flash_drive: FlashDriveConfigs, + pub virtio: VirtIOConfigs, + pub cmio: CmioConfig, + pub pmas: PmasConfig, + pub uarch: UarchConfig, + pub hash_tree: HashTreeConfig, +} + +impl MachineConfig { + /// Starts from the library's default config and overrides only the RAM + /// block. Useful for the common case where callers want the emulator's + /// baseline configuration plus a specific RAM image. + pub fn new_with_ram(ram: RAMConfig) -> Self { + let mut cfg = library_default(); + cfg.ram = ram; + cfg + } +} + +/// Fetches the emulator's built-in default config via `cm_get_default_config`. +/// All `Default` impls in this file delegate here rather than synthesizing +/// zeros in Rust, because C-side defaults carry non-trivial values like +/// `mvendorid`, `marchid`, initial `misa`, and the DTB `bootargs` string. +fn library_default() -> MachineConfig { + crate::machine::Machine::default_config() + .expect("failed to get default machine config from cartesi-machine library") +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; + use crate::machine::Machine; #[test] fn test_default_configs() { - default_config(); + library_default(); ProcessorConfig::default(); DTBConfig::default(); - TLBConfig::default(); + MemoryRangeConfig::default(); CLINTConfig::default(); PLICConfig::default(); HTIFConfig::default(); UarchProcessorConfig::default(); - UarchRAMConfig::default(); UarchConfig::default(); CmioConfig::default(); + HashTreeConfig::default(); + } + + /// Guardrail against silent schema drift between the Rust bindings and + /// the C++ `cartesi::machine_config`. Loads the default config as raw + /// JSON, deserializes it into `MachineConfig`, re-serializes, and + /// asserts structural equality with the original JSON. + /// + /// If this test fails after an emulator bump, do NOT add + /// `#[serde(default)]` to make it pass — the right fix is to update + /// this file's structs to match whatever the C++ side now emits. + #[test] + fn test_default_config_json_roundtrip() { + let raw_json = Machine::default_config_raw_json() + .expect("failed to fetch raw default config JSON"); + + let original: serde_json::Value = serde_json::from_str(&raw_json) + .expect("raw JSON from cm_get_default_config is not valid JSON"); + + let typed: MachineConfig = serde_json::from_str(&raw_json).unwrap_or_else(|e| { + panic!( + "failed to deserialize cm_get_default_config JSON into MachineConfig: {e}\n\ + (this usually means a schema drift between the emulator and these bindings)" + ); + }); + + let reserialized = serde_json::to_value(&typed).expect("re-serialization failed"); + + assert_eq!( + original, reserialized, + "MachineConfig round-trip lost or added data. Schema drift vs the C++ side." + ); + } + + /// Guardrail: makes sure an unknown field at the top level fails rather + /// than being silently dropped. Regression test for the bug this + /// refactor is fixing. + #[test] + fn test_unknown_field_is_rejected() { + let raw_json = Machine::default_config_raw_json() + .expect("failed to fetch raw default config JSON"); + + // Inject an unknown top-level field. + let mut value: serde_json::Value = serde_json::from_str(&raw_json).unwrap(); + value + .as_object_mut() + .unwrap() + .insert("something_new".to_string(), serde_json::json!(42)); + + let result = serde_json::from_value::(value); + assert!( + result.is_err(), + "deny_unknown_fields must reject previously-unseen top-level keys" + ); } } diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 2bc755538..2dd1502ac 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -63,16 +63,24 @@ impl Machine { // API functions // ----------------------------------------------------------------------------- - /// Returns the default machine config. + /// Returns the default machine config as parsed by serde. pub fn default_config() -> Result { + let raw = Self::default_config_raw_json()?; + Ok(serde_json::from_str(&raw) + .expect("cm_get_default_config returned JSON that does not match MachineConfig")) + } + + /// Returns the raw JSON string produced by `cm_get_default_config`, + /// without deserializing into a typed struct. Primarily used by the + /// round-trip schema test. + pub fn default_config_raw_json() -> Result { let mut config_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_default_config(ptr::null(), &mut config_ptr) }; check_err!(err_code)?; - let config = parse_json_from_cstring!(config_ptr); - - Ok(config) + let cstr = unsafe { CStr::from_ptr(config_ptr) }; + Ok(cstr.to_string_lossy().into_owned()) } /// Gets the address of any x, f, or control state register. From d66006a24e87fd31e168c681c892caf9fd6554ce Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Wed, 22 Apr 2026 20:12:10 -0300 Subject: [PATCH 013/113] refactor: harden v0.20 bindings against schema drift --- .../node/blockchain-reader/src/test_utils.rs | 29 +- .../node/state-manager/src/rollups_machine.rs | 18 +- .../cartesi-machine/src/config/machine.rs | 34 ++- .../cartesi-machine/src/config/runtime.rs | 261 ++++++++++++++++-- .../cartesi-machine/src/constants.rs | 25 +- .../rust-bindings/cartesi-machine/src/lib.rs | 19 ++ .../cartesi-machine/src/machine.rs | 163 +++++++++-- prt/client-lua/computation/constants.lua | 11 +- prt/client-rs/core/src/machine/constants.rs | 61 ++++ prt/client-rs/core/src/machine/instance.rs | 36 +-- prt/tests/rollups/justfile | 2 +- 11 files changed, 545 insertions(+), 114 deletions(-) diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index 9b20bfcea..e618dc022 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -9,13 +9,10 @@ use alloy::{ signers::{Signer, local::PrivateKeySigner}, }; use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory::{self, WithdrawalConfig}; +use cartesi_machine::{Machine, config::runtime::RuntimeConfig}; use cartesi_rollups_contracts::i_input_box::IInputBox; use serde::Deserialize; -use std::{ - fs::{self, File}, - io::{Read, Seek}, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; type Result = std::result::Result>; @@ -76,15 +73,19 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let input_box = deployment_address("InputBox"); let dave_app_factory = deployment_address("DaveAppFactory"); - let initial_hash = { - // Root hash is stored in hash_tree.sht at offset 0x60 (node 1's hash in sparse tree). - // Equivalent to: xxd -seek 0x60 -l 0x20 -c 0x20 -p .../machine-image/hash_tree.sht - let mut file = - File::open(program_path.join("machine-image").join("hash_tree.sht")).unwrap(); - file.seek(std::io::SeekFrom::Start(0x60)).unwrap(); - let mut buffer = [0u8; 32]; - file.read_exact(&mut buffer).unwrap(); - buffer + // Load the stored machine through the emulator and ask it for the root + // hash, rather than reading the internal `hash_tree.sht` file directly. + // The file layout is an emulator implementation detail; going through + // `cm_load_new` + `cm_get_root_hash` is the only stable API. + let initial_hash: [u8; 32] = { + let mut machine = Machine::load( + &program_path.join("machine-image"), + &RuntimeConfig::quiet_console(), + ) + .expect("failed to load stored machine"); + machine + .root_hash() + .expect("failed to read machine root hash") }; let withdrawal_config = WithdrawalConfig { diff --git a/cartesi-rollups/node/state-manager/src/rollups_machine.rs b/cartesi-rollups/node/state-manager/src/rollups_machine.rs index 8c0a9d5fc..2c6473b23 100644 --- a/cartesi-rollups/node/state-manager/src/rollups_machine.rs +++ b/cartesi-rollups/node/state-manager/src/rollups_machine.rs @@ -4,13 +4,14 @@ use std::path::{Path, PathBuf}; use cartesi_prt_core::machine::constants::{ - LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, + CHECKPOINT_ADDRESS, LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, + LOG2_UARCH_SPAN_TO_BARCH, }; use crate::{CommitmentLeaf, Proof}; use cartesi_machine::{ - config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::{break_reason, machine::TREE_LOG2_ROOT_SIZE, pma::TX_START}, + config::runtime::RuntimeConfig, + constants::{ar::TX_START, break_reason, machine::HASH_TREE_LOG2_ROOT_SIZE}, error::{MachineError, MachineResult}, machine::Machine, types::{ @@ -45,8 +46,6 @@ pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE); -pub const CHECKPOINT_ADDRESS: u64 = 0xfe0; - pub struct RollupsMachine { machine: Machine, epoch_number: u64, @@ -59,12 +58,7 @@ impl RollupsMachine { epoch_number: u64, next_input_index_in_epoch: u64, ) -> MachineResult { - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let machine = Machine::load(path, &runtime_config)?; Ok(Self { @@ -88,7 +82,7 @@ impl RollupsMachine { } pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { - let proof = self.machine.proof(TX_START, 5, TREE_LOG2_ROOT_SIZE)?; + let proof = self.machine.proof(TX_START, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let siblings = Proof::new(proof.sibling_hashes); let output_merkle = self.machine.read_memory(TX_START, 32)?; diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index 5fb933025..0905483ba 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -322,9 +322,13 @@ pub enum VirtIODeviceConfig { host_directory: String, }, #[serde(rename = "net-user")] - NetUser { hostfwd: VirtIOHostfwdArray }, + NetUser { + hostfwd: VirtIOHostfwdArray, + }, #[serde(rename = "net-tuntap")] - NetTuntap { iface: String }, + NetTuntap { + iface: String, + }, } impl Default for VirtIODeviceConfig { @@ -502,6 +506,24 @@ fn library_default() -> MachineConfig { mod tests { use super::*; use crate::machine::Machine; + use crate::{EXPECTED_EMULATOR_VERSION, format_emulator_version}; + + /// Guardrail: the linked `libcartesi` must report the exact version these + /// bindings were written against. If this fails after an emulator bump, + /// update `EXPECTED_EMULATOR_VERSION` in `lib.rs` and rerun the config + /// round-trip tests to re-confirm the schema. + #[test] + fn test_emulator_version_pin() { + let linked = Machine::version(); + assert_eq!( + linked, + EXPECTED_EMULATOR_VERSION, + "cartesi-machine bindings were written for emulator version {}, but libcartesi reports {}. \ + Update EXPECTED_EMULATOR_VERSION after verifying the config schema still matches.", + format_emulator_version(EXPECTED_EMULATOR_VERSION), + format_emulator_version(linked), + ); + } #[test] fn test_default_configs() { @@ -528,8 +550,8 @@ mod tests { /// this file's structs to match whatever the C++ side now emits. #[test] fn test_default_config_json_roundtrip() { - let raw_json = Machine::default_config_raw_json() - .expect("failed to fetch raw default config JSON"); + let raw_json = + Machine::default_config_raw_json().expect("failed to fetch raw default config JSON"); let original: serde_json::Value = serde_json::from_str(&raw_json) .expect("raw JSON from cm_get_default_config is not valid JSON"); @@ -554,8 +576,8 @@ mod tests { /// refactor is fixing. #[test] fn test_unknown_field_is_rejected() { - let raw_json = Machine::default_config_raw_json() - .expect("failed to fetch raw default config JSON"); + let raw_json = + Machine::default_config_raw_json().expect("failed to fetch raw default config JSON"); // Inject an unknown top-level field. let mut value: serde_json::Value = serde_json::from_str(&raw_json).unwrap(); diff --git a/machine/rust-bindings/cartesi-machine/src/config/runtime.rs b/machine/rust-bindings/cartesi-machine/src/config/runtime.rs index 6f131d4a6..2c7d191bf 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/runtime.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/runtime.rs @@ -1,32 +1,251 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +//! Rust mirror of `cartesi::machine_runtime_config` from the v0.20 cartesi-machine +//! C++ API. Follows the same invariants as `config::machine`: +//! +//! 1. Every struct carries `#[serde(deny_unknown_fields)]` to surface future +//! schema additions as explicit deserialization failures. +//! 2. No speculative `#[serde(default)]` on fields the C++ `to_json` emits +//! unconditionally (all of them, here). +//! 3. A round-trip test (`test_runtime_config_schema_stability`) pins the +//! v0.20 shape so silent drift is impossible. + use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ConcurrencyRuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub update_merkle_tree: Option, +// --------------------------------------------------------------------------- +// Console configuration +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::console_output_destination`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleOutputDestination { + ToNull, + ToStdout, + ToStderr, + ToFd, + ToFile, + ToBuffer, +} + +impl Default for ConsoleOutputDestination { + /// Matches the C++ in-struct initializer (`console_output_destination::to_stdout`). + fn default() -> Self { + Self::ToStdout + } +} + +/// Mirror of C++ `cartesi::console_flush_mode`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleFlushMode { + WhenFull, + EveryChar, + EveryLine, +} + +impl Default for ConsoleFlushMode { + /// Matches the C++ in-struct initializer (`console_flush_mode::every_line`). + fn default() -> Self { + Self::EveryLine + } +} + +/// Mirror of C++ `cartesi::console_input_source`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleInputSource { + FromNull, + FromStdin, + FromFd, + FromFile, + FromBuffer, +} + +impl Default for ConsoleInputSource { + /// Matches the C++ in-struct initializer (`console_input_source::from_null`). + fn default() -> Self { + Self::FromNull + } +} + +/// Mirror of C++ `cartesi::console_runtime_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ConsoleRuntimeConfig { + pub output_destination: ConsoleOutputDestination, + pub output_flush_mode: ConsoleFlushMode, + pub output_buffer_size: u64, + pub output_fd: i32, + pub output_filename: String, + + pub input_source: ConsoleInputSource, + pub input_buffer_size: u64, + pub input_fd: i32, + pub input_filename: String, + + pub tty_cols: u16, + pub tty_rows: u16, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct HTIFRuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub no_console_putchar: Option, +impl Default for ConsoleRuntimeConfig { + /// Matches the in-struct initializers in `machine-runtime-config.h` and the + /// `os::TTY_DEFAULT_*` constants from v0.20 (`os.h`: cols=80, rows=25). + fn default() -> Self { + Self { + output_destination: ConsoleOutputDestination::default(), + output_flush_mode: ConsoleFlushMode::default(), + output_buffer_size: 4096, + output_fd: -1, + output_filename: String::new(), + + input_source: ConsoleInputSource::default(), + input_buffer_size: 4096, + input_fd: -1, + input_filename: String::new(), + + tty_cols: 80, + tty_rows: 25, + } + } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Concurrency and top-level runtime configuration +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::concurrency_runtime_config`. Note: v0.19's +/// `update_merkle_tree` field was renamed to `update_hash_tree` in v0.20. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ConcurrencyRuntimeConfig { + pub update_hash_tree: u64, +} + +/// Mirror of C++ `cartesi::machine_runtime_config`. +/// +/// The v0.19 binding had top-level `htif`, `skip_root_hash_check`, and +/// `skip_root_hash_store` fields. None of those exist on the v0.20 +/// `machine_runtime_config`. The equivalent of v0.19's +/// `htif.no_console_putchar` is now `console.output_destination = ToNull`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct RuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub concurrency: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub htif: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_root_hash_check: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_root_hash_store: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_version_check: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub soft_yield: Option, + pub console: ConsoleRuntimeConfig, + pub concurrency: ConcurrencyRuntimeConfig, + pub skip_version_check: bool, + pub soft_yield: bool, + pub no_reserve: bool, +} + +impl RuntimeConfig { + /// Convenience for "run the machine without touching the host console" — + /// replaces the v0.19 pattern of setting `htif.no_console_putchar = true`. + pub fn quiet_console() -> Self { + Self { + console: ConsoleRuntimeConfig { + output_destination: ConsoleOutputDestination::ToNull, + input_source: ConsoleInputSource::FromNull, + ..ConsoleRuntimeConfig::default() + }, + ..Self::default() + } + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + /// Static schema-completeness test: pins the v0.20 `machine_runtime_config` + /// JSON shape directly against `RuntimeConfig`, so drift in either + /// direction surfaces as a test failure. + /// + /// The JSON below is constructed from `src/json-util.cpp::to_json + /// (machine_runtime_config)` and the default values in + /// `machine-runtime-config.h` / `os.h` (TTY_DEFAULT_COLS=80, + /// TTY_DEFAULT_ROWS=25). + #[test] + fn test_runtime_config_schema_stability() { + let v020_json = serde_json::json!({ + "console": { + "output_destination": "to_stdout", + "output_flush_mode": "every_line", + "output_buffer_size": 4096u64, + "output_fd": -1, + "output_filename": "", + "input_source": "from_null", + "input_buffer_size": 4096u64, + "input_fd": -1, + "input_filename": "", + "tty_cols": 80, + "tty_rows": 25, + }, + "concurrency": { "update_hash_tree": 0u64 }, + "skip_version_check": false, + "soft_yield": false, + "no_reserve": false, + }); + + let typed: RuntimeConfig = serde_json::from_value(v020_json.clone()) + .expect("v0.20 runtime JSON should parse into RuntimeConfig"); + let reserialized = serde_json::to_value(&typed).expect("re-serialization failed"); + + assert_eq!( + v020_json, reserialized, + "RuntimeConfig round-trip lost or added data. Schema drift vs the C++ side." + ); + } + + #[test] + fn test_runtime_config_default_round_trips() { + let cfg = RuntimeConfig::default(); + let json = serde_json::to_value(&cfg).expect("serialization should succeed"); + let back: RuntimeConfig = + serde_json::from_value(json).expect("deserialization should succeed"); + assert_eq!(cfg, back); + } + + #[test] + fn test_runtime_config_quiet_console() { + let cfg = RuntimeConfig::quiet_console(); + assert_eq!( + cfg.console.output_destination, + ConsoleOutputDestination::ToNull + ); + assert_eq!(cfg.console.input_source, ConsoleInputSource::FromNull); + } + + #[test] + fn test_runtime_config_unknown_field_rejected() { + let json = serde_json::json!({ + "console": { + "output_destination": "to_stdout", + "output_flush_mode": "every_line", + "output_buffer_size": 4096u64, + "output_fd": -1, + "output_filename": "", + "input_source": "from_null", + "input_buffer_size": 4096u64, + "input_fd": -1, + "input_filename": "", + "tty_cols": 80, + "tty_rows": 25, + }, + "concurrency": { "update_hash_tree": 0u64 }, + "skip_version_check": false, + "soft_yield": false, + "no_reserve": false, + "something_new": true, + }); + assert!( + serde_json::from_value::(json).is_err(), + "deny_unknown_fields must reject previously-unseen top-level keys" + ); + } } diff --git a/machine/rust-bindings/cartesi-machine/src/constants.rs b/machine/rust-bindings/cartesi-machine/src/constants.rs index fef0a3752..92403e416 100644 --- a/machine/rust-bindings/cartesi-machine/src/constants.rs +++ b/machine/rust-bindings/cartesi-machine/src/constants.rs @@ -1,24 +1,39 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -//! Constants definitions from Cartesi Machine +//! Constants definitions from Cartesi Machine. +//! +//! The names in this module track the v0.20 emulator naming convention: +//! `HASH_TREE_LOG2_*` for hash-tree sizes (previously `TREE_LOG2_*`) and the +//! `ar` module for address ranges (previously `pma`). The numeric values are +//! unchanged from v0.19. pub mod machine { use cartesi_machine_sys::*; // pub const CYCLE_MAX: u64 = CM_MCYCLE_MAX as u64; pub const HASH_SIZE: u32 = CM_HASH_SIZE as u32; - pub const TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; - pub const TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; - pub const TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; + pub const HASH_TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; + pub const HASH_TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; + pub const HASH_TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; } -pub mod pma { +pub mod ar { use cartesi_machine_sys::*; pub const RX_START: u64 = CM_AR_CMIO_RX_BUFFER_START as u64; pub const RX_LOG2_SIZE: u64 = CM_AR_CMIO_RX_BUFFER_LOG2_SIZE as u64; pub const TX_START: u64 = CM_AR_CMIO_TX_BUFFER_START as u64; pub const TX_LOG2_SIZE: u64 = CM_AR_CMIO_TX_BUFFER_LOG2_SIZE as u64; pub const RAM_START: u64 = CM_AR_RAM_START as u64; + /// Dedicated memory slot the off-chain client writes the pre-input root + /// hash to before sending a CMIO input, so that on-chain + /// `revertIfNeeded` can read it back after a rejected input. + /// + /// Canonical source is the emulator C++; the Solidity side mirrors it + /// through the auto-generated + /// `step/src/EmulatorConstants.sol::REVERT_ROOT_HASH_ADDRESS`, used + /// only from `EmulatorCompat.{set,get}RevertRootHash` wrappers — no + /// other Solidity file should reference the raw address. + pub const SHADOW_REVERT_ROOT_HASH_START: u64 = CM_AR_SHADOW_REVERT_ROOT_HASH_START as u64; } pub mod break_reason { diff --git a/machine/rust-bindings/cartesi-machine/src/lib.rs b/machine/rust-bindings/cartesi-machine/src/lib.rs index 06c0222a3..b5b4cb621 100644 --- a/machine/rust-bindings/cartesi-machine/src/lib.rs +++ b/machine/rust-bindings/cartesi-machine/src/lib.rs @@ -13,3 +13,22 @@ pub use machine::Machine; // Reexport inner cartesi-machine-sys pub use cartesi_machine_sys; + +/// Emulator semantic version these bindings were written against, encoded per +/// the convention from `machine-c-api.h`: +/// `(major * 1000000) + (minor * 1000) + patch`. +/// +/// The `test_emulator_version_pin` test asserts at build time that the linked +/// `libcartesi` reports this exact version. Bumping the emulator requires +/// bumping this constant and re-running the config round-trip tests — any +/// schema drift will surface there. +pub const EXPECTED_EMULATOR_VERSION: u64 = 20_000; // 0.20.0 + +/// Formats an emulator version u64 (as returned by `cm_get_version`) as +/// `"major.minor.patch"`. +pub fn format_emulator_version(v: u64) -> String { + let major = v / 1_000_000; + let minor = (v / 1_000) % 1_000; + let patch = v % 1_000; + format!("{major}.{minor}.{patch}") +} diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 2dd1502ac..4e45f9f6c 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -20,15 +20,28 @@ use crate::{ }, }; -/// Machine instance handle +/// Machine instance handle. +/// +/// Owns a `*mut cm_machine` and frees it on `Drop` via `cm_delete`. The raw +/// pointer is kept private — exposing it would let callers clone it and cause +/// a double-free when both `Machine`s get dropped. +/// +/// `Machine` is intentionally `!Send + !Sync` (the default, given the raw +/// pointer field). Do not add `unsafe impl Send for Machine` without auditing +/// `cm_get_last_error_message`: the C library threads error messages through +/// thread-local (or global) state with no machine-instance argument, so two +/// `Machine`s running on different threads could scramble each other's +/// `MachineError::message` fields. pub struct Machine { - pub machine: *mut cartesi_machine_sys::cm_machine, + machine: *mut cartesi_machine_sys::cm_machine, } impl Drop for Machine { fn drop(&mut self) { - unsafe { - cartesi_machine_sys::cm_delete(self.machine); + if !self.machine.is_null() { + unsafe { + cartesi_machine_sys::cm_delete(self.machine); + } } } } @@ -43,6 +56,16 @@ macro_rules! check_err { }; } +/// Both `serde_json::to_string` and `CString::new` below panic on failure +/// *by design*. They can only fail on: +/// - A Rust config type holding non-serializable state. Our types are plain +/// POD with derived `Serialize`, so this is statically impossible. +/// - A JSON string containing an interior NUL byte. `serde_json` escapes NUL +/// as `\u0000`, so the output is guaranteed NUL-free. +/// +/// If either panic ever fires, it indicates a bug in this crate or in +/// `serde_json` — there is no recovery, and a panic with a backtrace is more +/// debuggable than a bubbled-up `Result` that crashes at the caller anyway. macro_rules! serialize_to_json { ($src:expr) => { CString::new(serde_json::to_string($src).expect("failed serializing to json")) @@ -50,6 +73,13 @@ macro_rules! serialize_to_json { }; } +/// Panics on malformed JSON from the C library. This means either a bug in +/// `libcartesi` or a mismatch between the Rust struct shape and the +/// emulator's JSON schema — the round-trip tests in `config/machine.rs` and +/// `config/runtime.rs` are expected to catch the latter before production. +/// A `Result` return here would force every call site to propagate an error +/// variant for a condition with no meaningful recovery path; panicking gives +/// a clearer stacktrace. macro_rules! parse_json_from_cstring { ($src:expr) => {{ let cstr = unsafe { CStr::from_ptr($src) }; @@ -63,6 +93,13 @@ impl Machine { // API functions // ----------------------------------------------------------------------------- + /// Returns the emulator semantic version of the linked `libcartesi`, as + /// returned by `cm_get_version`. Encoded as + /// `(major * 1000000) + (minor * 1000) + patch`. + pub fn version() -> u64 { + unsafe { cartesi_machine_sys::cm_get_version() } + } + /// Returns the default machine config as parsed by serde. pub fn default_config() -> Result { let raw = Self::default_config_raw_json()?; @@ -118,7 +155,7 @@ impl Machine { /// Loads a new machine instance from a previously stored directory. pub fn load(dir: &Path, runtime_config: &RuntimeConfig) -> Result { - let dir_cstr = path_to_cstring(dir); + let dir_cstr = path_to_cstring(dir)?; let runtime_config_json = serialize_to_json!(&runtime_config); let mut machine: *mut cartesi_machine_sys::cm_machine = ptr::null_mut(); @@ -140,7 +177,7 @@ impl Machine { /// address ranges (required when storing in-memory machines that have no /// backing files). pub fn store(&mut self, dir: &Path) -> Result<()> { - let dir_cstr = path_to_cstring(dir); + let dir_cstr = path_to_cstring(dir)?; let err_code = unsafe { cartesi_machine_sys::cm_store( self.machine, @@ -164,19 +201,42 @@ impl Machine { Ok(()) } - /// Gets the machine runtime config. + /// Gets the machine runtime config as parsed by serde. pub fn runtime_config(&mut self) -> Result { + let raw = self.runtime_config_raw_json()?; + Ok(serde_json::from_str(&raw) + .expect("cm_get_runtime_config returned JSON that does not match RuntimeConfig")) + } + + /// Returns the raw JSON string produced by `cm_get_runtime_config`. Used + /// by the round-trip schema test. + pub fn runtime_config_raw_json(&mut self) -> Result { let mut rc_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_runtime_config(self.machine, &mut rc_ptr) }; check_err!(err_code)?; - let runtime_config = parse_json_from_cstring!(rc_ptr); - - Ok(runtime_config) + let cstr = unsafe { CStr::from_ptr(rc_ptr) }; + Ok(cstr.to_string_lossy().into_owned()) } /// Replaces a memory range. + /// + /// Two intentional simplifications vs. the full JSON schema the C API + /// accepts: + /// + /// - `read_only` is hardcoded to `false`. The C++ + /// `machine_address_ranges::replace` explicitly rejects both a + /// read-only existing range and a replacement config with + /// `read_only: true` (see `machine-address-ranges.cpp`), so exposing a + /// `read_only` toggle here would always error. If that ever changes, + /// widen this API then. + /// - When `image_path` is `None`, `data_filename` is serialized as the + /// empty string. The C++ side treats empty `data_filename` as "no + /// backing store" (`backing_store_config::newly_created()` returns + /// true when `create || data_filename.empty()`), which is the same + /// semantics as the old API's `NULL` pointer: the range is + /// zero-filled in-memory. pub fn replace_memory_range( &mut self, start: u64, @@ -249,7 +309,7 @@ impl Machine { cartesi_machine_sys::cm_get_proof( self.machine, address, - log2_target_size as i32, + log2_target_size as ::std::os::raw::c_int, log2_root_size as ::std::os::raw::c_int, &mut proof_ptr, ) @@ -294,7 +354,7 @@ impl Machine { /// Reads a chunk of data from a machine memory range, by its physical address. pub fn read_memory(&mut self, address: u64, size: u64) -> Result> { - let mut buffer = vec![0u8; size as usize]; + let mut buffer = vec![0u8; u64_to_usize(size)?]; let err_code = unsafe { cartesi_machine_sys::cm_read_memory(self.machine, address, buffer.as_mut_ptr(), size) }; @@ -320,7 +380,7 @@ impl Machine { /// Reads a chunk of data from a machine memory range, by its virtual memory. pub fn read_virtual_memory(&mut self, address: u64, size: u64) -> Result> { - let mut buffer = vec![0u8; size as usize]; + let mut buffer = vec![0u8; u64_to_usize(size)?]; let err_code = unsafe { cartesi_machine_sys::cm_read_virtual_memory( self.machine, @@ -428,7 +488,10 @@ impl Machine { let mut reason: u16 = 0; let mut length: u64 = 0; - // if data is NULL, length will still be set without reading any data. + // First call with a NULL data pointer: the C API just writes the + // required length into `length` and returns, without reading any + // bytes. (See machine-c-api.h: "If NULL, length will still be set + // without reading any data.") let err_code = unsafe { cartesi_machine_sys::cm_receive_cmio_request( self.machine, @@ -440,7 +503,11 @@ impl Machine { }; check_err!(err_code)?; - let mut buffer = vec![0u8; length as usize]; + // `length` is in-out per the C API contract ("Must be initialized to + // the size of data buffer"). Sizing the buffer to exactly `length` + // and then passing the same value back in makes the buffer-size + // precondition and the required-length output coincide. + let mut buffer = vec![0u8; u64_to_usize(length)?]; let err_code = unsafe { cartesi_machine_sys::cm_receive_cmio_request( @@ -478,7 +545,7 @@ impl Machine { /// Runs the machine for the given mcycle count and generates a log of accessed pages and proof data. pub fn log_step(&mut self, mcycle_count: u64, log_filename: &Path) -> Result { let mut break_reason = BreakReason::default(); - let log_filename_c = path_to_cstring(log_filename); + let log_filename_c = path_to_cstring(log_filename)?; let err_code = unsafe { cartesi_machine_sys::cm_log_step( @@ -563,7 +630,7 @@ impl Machine { mcycle_count: u64, root_hash_after: &Hash, ) -> Result { - let log_filename_c = path_to_cstring(log_filename); + let log_filename_c = path_to_cstring(log_filename)?; let mut break_reason = BreakReason::default(); let err_code = unsafe { @@ -590,6 +657,9 @@ impl Machine { let err_code = unsafe { cartesi_machine_sys::cm_verify_step_uarch( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. (cm_verify_step itself doesn't take this + // argument — the asymmetry is intentional in the C API.) ptr::null(), root_hash_before, log_cstr.as_ptr(), @@ -610,6 +680,8 @@ impl Machine { let log_cstr = serialize_to_json!(&log); let err_code = unsafe { cartesi_machine_sys::cm_verify_reset_uarch( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. ptr::null(), root_hash_before, log_cstr.as_ptr(), @@ -633,6 +705,8 @@ impl Machine { let err_code = unsafe { cartesi_machine_sys::cm_verify_send_cmio_response( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. ptr::null(), reason as u16, data.as_ptr(), @@ -660,8 +734,49 @@ impl Machine { } } -fn path_to_cstring(path: &Path) -> CString { - CString::new(path.to_string_lossy().as_bytes()).expect("CString::new failed") +/// Converts a `u64` byte count (as used by the C API) to a Rust `usize`, +/// erroring out if the value exceeds what the platform can address. Only +/// matters on 32-bit targets — on 64-bit, `usize` and `u64` are the same +/// width and this is a no-op. Guards against silent truncation that would +/// result in an undersized buffer being passed to a C function expecting +/// `size` bytes of space. +fn u64_to_usize(size: u64) -> Result { + usize::try_from(size).map_err(|_| MachineError { + code: constants::error_code::OUT_OF_RANGE, + message: format!("byte count {size} exceeds usize range on this platform"), + }) +} + +/// Converts a `Path` to a `CString` for the C API. +/// +/// On Unix, uses the raw `OsStr` bytes so that non-UTF-8 paths (which are +/// legal on the platform) are passed through verbatim instead of being +/// silently corrupted by `to_string_lossy` replacement. On other platforms, +/// falls back to UTF-8 conversion and errors out if the path is not valid +/// UTF-8. +/// +/// Returns `CM_ERROR_INVALID_ARGUMENT` on an interior NUL byte or, on +/// non-Unix, on a non-UTF-8 path. +fn path_to_cstring(path: &Path) -> Result { + #[cfg(unix)] + let bytes = { + use std::os::unix::ffi::OsStrExt; + path.as_os_str().as_bytes().to_vec() + }; + #[cfg(not(unix))] + let bytes = path + .to_str() + .ok_or_else(|| MachineError { + code: constants::error_code::INVALID_ARGUMENT, + message: format!("path is not valid UTF-8: {}", path.display()), + })? + .as_bytes() + .to_vec(); + + CString::new(bytes).map_err(|e| MachineError { + code: constants::error_code::INVALID_ARGUMENT, + message: format!("path contains NUL byte ({}): {}", e, path.display()), + }) } #[cfg(test)] @@ -722,13 +837,7 @@ mod tests { } fn create_machine(config: &MachineConfig) -> Result { - let runtime_config = RuntimeConfig { - htif: Some(config::runtime::HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; - Machine::create(config, &runtime_config) + Machine::create(config, &RuntimeConfig::quiet_console()) } #[test] @@ -951,7 +1060,7 @@ mod tests { let proof: Proof = machine.proof( range.start, u64::BITS - range.length.leading_zeros(), - constants::machine::TREE_LOG2_ROOT_SIZE, + constants::machine::HASH_TREE_LOG2_ROOT_SIZE, )?; assert_eq!(proof.target_address, range.start); assert_eq!(proof.log2_target_size, log2_size as u64); diff --git a/prt/client-lua/computation/constants.lua b/prt/client-lua/computation/constants.lua index add83f6aa..5bcb878a4 100644 --- a/prt/client-lua/computation/constants.lua +++ b/prt/client-lua/computation/constants.lua @@ -1,4 +1,5 @@ local arithmetic = require "utils.arithmetic" +local cartesi = require "cartesi" -- log2 value of the maximal number of micro instructions that emulates a big instruction local log2_uarch_span_to_barch = 20 @@ -11,8 +12,14 @@ local log2_uarch_span_to_input = log2_uarch_span_to_barch + log2_barch_span_to_i -- log2 value of the maximal number of meta instructions local log2_uarch_span_to_epoch = log2_input_span_to_epoch + log2_barch_span_to_input + log2_uarch_span_to_barch --- Checkpoint address for machine state snapshots -local CHECKPOINT_ADDRESS = 0xfe0 +-- Memory slot where the off-chain client writes the pre-input root hash +-- before sending a CMIO input, so that on-chain `revertIfNeeded` can read it +-- back after a rejected input. Sourced from the emulator directly (v0.20+ +-- `cartesi.AR_SHADOW_REVERT_ROOT_HASH_START`, currently 0xfe0); the Solidity +-- side mirrors this through step's auto-generated +-- `EmulatorConstants.REVERT_ROOT_HASH_ADDRESS`. +local CHECKPOINT_ADDRESS = cartesi.AR_SHADOW_REVERT_ROOT_HASH_START +assert(CHECKPOINT_ADDRESS, "emulator missing AR_SHADOW_REVERT_ROOT_HASH_START (expected v0.20+)") local constants = { log2_uarch_span_to_barch = log2_uarch_span_to_barch, diff --git a/prt/client-rs/core/src/machine/constants.rs b/prt/client-rs/core/src/machine/constants.rs index 97f6654e2..6bc96821e 100644 --- a/prt/client-rs/core/src/machine/constants.rs +++ b/prt/client-rs/core/src/machine/constants.rs @@ -14,3 +14,64 @@ pub const INPUT_SPAN_TO_EPOCH: u64 = arithmetic::max_uint(LOG2_INPUT_SPAN_TO_EPO // log2 value of the maximal number of micro instructions that executes an input pub const LOG2_UARCH_SPAN_TO_INPUT: u64 = LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH; + +/// Re-export of the emulator's dedicated memory slot for the pre-input root +/// hash (a.k.a. `CM_AR_SHADOW_REVERT_ROOT_HASH_START`, currently `0xfe0`). +/// +/// The off-chain client writes the current root hash to this address before +/// sending a CMIO input, so that on-chain `revertIfNeeded` can read it back +/// and restore the state after a rejected input. The Solidity side mirrors +/// the emulator through step's auto-generated +/// `EmulatorConstants.REVERT_ROOT_HASH_ADDRESS`; +/// `tests::test_emulator_and_step_agree_on_revert_address` asserts the two +/// stay in sync after any emulator or step bump. +pub use cartesi_machine::constants::ar::SHADOW_REVERT_ROOT_HASH_START as CHECKPOINT_ADDRESS; + +#[cfg(test)] +mod tests { + use super::CHECKPOINT_ADDRESS; + + /// Guardrail: step's `EmulatorConstants.sol` is auto-generated from the + /// emulator C++ source, and `REVERT_ROOT_HASH_ADDRESS` must equal the + /// emulator's `CM_AR_SHADOW_REVERT_ROOT_HASH_START` — otherwise the + /// off-chain client writes to one address while on-chain + /// `revertIfNeeded` reads from another, and any rejected-input dispute + /// mis-restores state. If this test fails after an emulator or step + /// bump, the step submodule is out of sync with the emulator version + /// these bindings link against: regenerate step's `EmulatorConstants.sol` + /// against the matching emulator and bump both submodule pointers + /// together. + #[test] + fn test_emulator_and_step_agree_on_revert_address() { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let emulator_constants_sol = manifest_dir + .join("../../..") + .join("machine/step/src/EmulatorConstants.sol"); + let source = std::fs::read_to_string(&emulator_constants_sol) + .unwrap_or_else(|e| panic!("failed to read {}: {e}", emulator_constants_sol.display())); + + // Find: `uint64 constant REVERT_ROOT_HASH_ADDRESS = 0x;` + let marker = "REVERT_ROOT_HASH_ADDRESS"; + let pos = source.find(marker).unwrap_or_else(|| { + panic!("{marker} not found in {}", emulator_constants_sol.display()) + }); + let after = &source[pos + marker.len()..]; + let eq = after.find('=').expect("expected `=` after constant name"); + let semi = after.find(';').expect("expected `;` after constant value"); + let value_str = after[eq + 1..semi].trim(); + let step_value = if let Some(hex) = value_str.strip_prefix("0x") { + u64::from_str_radix(hex, 16).expect("REVERT_ROOT_HASH_ADDRESS not valid hex") + } else { + value_str + .parse::() + .expect("REVERT_ROOT_HASH_ADDRESS not valid decimal") + }; + + assert_eq!( + CHECKPOINT_ADDRESS, step_value, + "Emulator CM_AR_SHADOW_REVERT_ROOT_HASH_START ({CHECKPOINT_ADDRESS:#x}) \ + does not match step's EmulatorConstants.REVERT_ROOT_HASH_ADDRESS ({step_value:#x}). \ + The off-chain client and on-chain verifier will disagree on the revert slot." + ); + } +} diff --git a/prt/client-rs/core/src/machine/instance.rs b/prt/client-rs/core/src/machine/instance.rs index 71b4b5b59..13fe5719e 100644 --- a/prt/client-rs/core/src/machine/instance.rs +++ b/prt/client-rs/core/src/machine/instance.rs @@ -1,15 +1,15 @@ use crate::db::dispute_state_access::DisputeStateAccess; use crate::machine::constants::{ - BARCH_SPAN_TO_INPUT, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, LOG2_UARCH_SPAN_TO_INPUT, - UARCH_SPAN_TO_BARCH, + BARCH_SPAN_TO_INPUT, CHECKPOINT_ADDRESS, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, + LOG2_UARCH_SPAN_TO_INPUT, UARCH_SPAN_TO_BARCH, }; use crate::machine::error::Result; use cartesi_dave_arithmetic as arithmetic; use cartesi_dave_merkle::Digest; use cartesi_machine::{ cartesi_machine_sys, - config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::machine::TREE_LOG2_ROOT_SIZE, + config::runtime::RuntimeConfig, + constants::machine::HASH_TREE_LOG2_ROOT_SIZE, machine::Machine, types::access_proof::AccessLog, types::{LogType, cmio::CmioResponseReason}, @@ -64,15 +64,9 @@ pub struct MachineInstance { pub snapshot_path: PathBuf, } -const CHECKPOINT_ADDRESS: u64 = 0xfe0; impl MachineInstance { pub fn new_from_path(path: &str) -> Result { - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let path = PathBuf::from(path); let mut machine = Machine::load(&path, &runtime_config)?; @@ -110,12 +104,7 @@ impl MachineInstance { // load inner machine with snapshot, update cycle, keep everything else the same pub fn load_snapshot(&mut self, snapshot_path: &Path, snapshot_cycle: u64) -> Result<()> { debug!("load snapshot from {}", snapshot_path.display()); - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let mut machine = Machine::load(Path::new(snapshot_path), &runtime_config)?; let cycle = machine.mcycle()?; @@ -257,12 +246,7 @@ impl MachineInstance { != cartesi_machine::constants::cmio::tohost::manual::RX_ACCEPTED { trace!("Reject input,revert to previous snapshot"); - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); self.machine = Machine::load(&self.snapshot_path, &runtime_config)?; } @@ -335,7 +319,7 @@ impl MachineInstance { let mut read = self.machine.read_memory(aligned_address, 32)?; let proof = self .machine - .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -355,7 +339,7 @@ impl MachineInstance { let read_hash = Digest::from_data(&read); let proof = self .machine - .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -375,7 +359,7 @@ impl MachineInstance { let read = self.machine.read_memory(address, 32)?; let read_hash = Digest::from_data(&read); // Get proof of write address - let proof = self.machine.proof(address, 5, TREE_LOG2_ROOT_SIZE)?; + let proof = self.machine.proof(address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); diff --git a/prt/tests/rollups/justfile b/prt/tests/rollups/justfile index 5aa45d935..d58390e9d 100644 --- a/prt/tests/rollups/justfile +++ b/prt/tests/rollups/justfile @@ -10,7 +10,7 @@ test PROGRAM SCRIPT: ANVIL_LOAD_PATH=`realpath {{ANVIL_LOAD_PATH}}` \ ANVIL_DUMP_PATH="anvil_{{PROGRAM}}_{{SCRIPT}}.json" \ TEMPLATE_MACHINE=`realpath ../../../test/programs/{{PROGRAM}}/machine-image` \ - TEMPLATE_MACHINE_HASH=0x`xxd -seek 0x60 -l 0x20 -c 0x20 -p ../../../test/programs/{{PROGRAM}}/machine-image/hash_tree.sht` \ + TEMPLATE_MACHINE_HASH=0x`cartesi-machine-stored-hash ../../../test/programs/{{PROGRAM}}/machine-image` \ DAVE_APP_FACTORY=`jq -r .address {{DEPLOYMENTS_DIR}}/DaveAppFactory.json` \ INPUT_BOX=`jq -r .address {{DEPLOYMENTS_DIR}}/InputBox.json` \ ERC20_PORTAL=`jq -r .address {{DEPLOYMENTS_DIR}}/ERC20Portal.json` \ From c2b4979649732cccd2921505e83616886bdfaf34 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 29 Apr 2026 18:20:01 -0300 Subject: [PATCH 014/113] feat: bump rollups-contracts from 3.0.0-alpha.3 to 3.0.0-alpha.4 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- cartesi-rollups/contracts/foundry.toml | 10 +++++----- cartesi-rollups/contracts/script/Deployment.s.sol | 2 +- cartesi-rollups/contracts/script/deploy.sh | 2 +- cartesi-rollups/contracts/soldeer.lock | 8 ++++---- cartesi-rollups/contracts/src/IDaveAppFactory.sol | 3 ++- cartesi-rollups/contracts/test/DaveAppFactory.t.sol | 12 ++++-------- 8 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b75aa03a8..dbb0d57e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1761,9 +1761,9 @@ dependencies = [ [[package]] name = "cartesi-rollups-contracts" -version = "2.1.1" +version = "3.0.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f5e916fa85a522e1caa0f4923184c31e6ea4ec851fbfe50b5a3846ea83a0c9" +checksum = "5155609bc75488ef8e2e11f80078eef7909688a43bcd9fd4df390d4aa4dc64f6" dependencies = [ "alloy", "serde", diff --git a/Cargo.toml b/Cargo.toml index 133a6ce7b..4b16169f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ cartesi-prt-core = { path = "prt/client-rs/core" } ## Dependencies # cartesi -cartesi-rollups-contracts = "=2.1.1" +cartesi-rollups-contracts = "=3.0.0-alpha.4" # eth alloy = { version = "1.0", features = [ diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index 5a3ad0cba..917fcf57c 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -8,10 +8,10 @@ via_ir = true allow_paths = ["../../prt/contracts", "../../machine/step"] remappings = [ - "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/dependencies/@openzeppelin-contracts-5.2.0/", + "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/dependencies/@openzeppelin-contracts-5.2.0/", "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/", - "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/dependencies/cartesi-machine-solidity-step-0.13.0/", - "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/", + "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/dependencies/cartesi-machine-solidity-step-0.13.0/", + "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/", "forge-std-1.9.6/=dependencies/forge-std-1.9.6/", "prt-contracts/=../../prt/contracts/src/", "step/=../../machine/step/", @@ -21,7 +21,7 @@ solc_version = "0.8.30" evm_version = "prague" fs_permissions = [ { access = "read-write", path = "deployments" }, - { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.3/deployments" }, + { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/deployments" }, { access = "read", path = "../../prt/contracts/deployments" }, ] @@ -38,4 +38,4 @@ exclude_lints = ["incorrect-shift"] [dependencies] "@openzeppelin-contracts" = "5.5.0" forge-std = "1.9.6" -cartesi-rollups-contracts = "3.0.0-alpha.3" +cartesi-rollups-contracts = "3.0.0-alpha.4" diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index 5974bfc1f..345eab16f 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -10,7 +10,7 @@ import {DaveAppFactory} from "src/DaveAppFactory.sol"; contract DeploymentScript is BaseDeploymentScript { function run() external { _importDeployments("../../prt/contracts"); - _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.3"); + _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.4"); address inputBox = _loadDeployment(".", "InputBox"); address appFactory = _loadDeployment(".", "ApplicationFactory"); diff --git a/cartesi-rollups/contracts/script/deploy.sh b/cartesi-rollups/contracts/script/deploy.sh index 82d972be6..7f7114ddf 100755 --- a/cartesi-rollups/contracts/script/deploy.sh +++ b/cartesi-rollups/contracts/script/deploy.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." roots=( '../../prt/contracts' - 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.3' + 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.4' '.' ) diff --git a/cartesi-rollups/contracts/soldeer.lock b/cartesi-rollups/contracts/soldeer.lock index 8058eeab8..580d8781d 100644 --- a/cartesi-rollups/contracts/soldeer.lock +++ b/cartesi-rollups/contracts/soldeer.lock @@ -7,10 +7,10 @@ integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" [[dependencies]] name = "cartesi-rollups-contracts" -version = "3.0.0-alpha.3" -url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_3_27-03-2026_20:38:21_rollups-contracts.zip" -checksum = "3d2ef3f5647f80d7549eed84b589fb612901c88c6f5f26cdf9b4a44d358b8b9a" -integrity = "8bb84e8623fc67af07bb7c096d49e0cc9cc1702a1cf600b7b1b8167ee1af1768" +version = "3.0.0-alpha.4" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_4_05-05-2026_13:59:19_rollups-contracts.zip" +checksum = "7e1f938a3f026d25672d060903d3df9f7838f89a4d5b3275235cf131bde084a9" +integrity = "d3019afac0ab8e35439c19c535689ad7857ffb59e0fdd319d757e533c77427b0" [[dependencies]] name = "forge-std" diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index 0c6a626c6..7715778da 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -5,13 +5,14 @@ pragma solidity ^0.8.8; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; +import {IApplicationFactoryErrors} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactoryErrors.sol"; import {IDaveConsensus} from "./IDaveConsensus.sol"; /// @title Dave-App Pair Factory /// @notice Allows anyone to reliably deploy an application /// validated a newly-deployed `IDaveConsensus` contract. -interface IDaveAppFactory { +interface IDaveAppFactory is IApplicationFactoryErrors { /// @notice A Dave-App pair was created. /// @param appContract The application contract /// @param daveConsensus The Dave consensus contract diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index b67b71fab..c67c4bb1d 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -15,6 +15,7 @@ import {ApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/Appli import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol"; +import {IApplicationFactoryErrors} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactoryErrors.sol"; import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; import {InputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/InputBox.sol"; import {LibBinaryMerkleTree} from "cartesi-rollups-contracts-3.0.0/src/library/LibBinaryMerkleTree.sol"; @@ -671,14 +672,9 @@ contract DaveAppFactoryTest is Test { function _testNewDaveAppFailure(WithdrawalConfig calldata withdrawalConfig, bytes memory errorData) internal pure { (bool isValidError, bytes32 errorSelector, bytes memory errorArgs) = errorData.consumeBytes4(); assertTrue(isValidError, "Expected error to contain a 4-byte selector"); - if (errorSelector == bytes4(keccak256("Error(string)"))) { - string memory errorMsg = abi.decode(errorArgs, (string)); - bytes32 errorMsgHash = keccak256(bytes(errorMsg)); - if (errorMsgHash == keccak256("Invalid withdrawal config")) { - assertFalse(withdrawalConfig.isValid(), "Expected withdrawal config to be invalid"); - } else { - revert("Unexpected error message"); - } + if (errorSelector == IApplicationFactoryErrors.InvalidWithdrawalConfig.selector) { + assertEq(errorArgs, abi.encode(withdrawalConfig), "Expected withdrawal configs to match"); + assertFalse(withdrawalConfig.isValid(), "Expected withdrawal config to be invalid"); } else { revert("Unexpected error"); } From 5d791d99c0b35dca6316bd489fbe036aef070832 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 6 May 2026 10:18:37 -0300 Subject: [PATCH 015/113] fix: bump dependencies - debian 12 (bookworm) -> 13 (trixie) - boost 1.81 -> 1.83 --- .github/workflows/build.yml | 4 ++-- test/Dockerfile | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebe9a7389..f6e34e676 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,7 +175,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') runs-on: ${{ matrix.os }} container: - image: rust:1.90-bookworm + image: rust:1.90-trixie env: DEBIAN_FRONTEND: noninteractive steps: @@ -189,7 +189,7 @@ jobs: apt-get install -y --no-install-recommends \ build-essential git wget curl \ liblua5.4-dev lua5.4 \ - libslirp-dev libboost1.81-dev \ + libslirp-dev libboost1.83-dev \ libclang-dev \ xxd jq sqlite3 diff --git a/test/Dockerfile b/test/Dockerfile index f05bc9313..82a25d9d3 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -1,12 +1,13 @@ # syntax=docker.io/docker/dockerfile:1 -ARG RUST_VERSION=1.86 +ARG RUST_VERSION=1.90 +ARG DEBIAN_VERSION=trixie ARG FOUNDRY_VERSION=1.4.3 ARG PNPM_VERSION=10.7.0 ARG JUST_VERSION=1.46.0 #### base stage -FROM rust:${RUST_VERSION} AS base +FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS base ARG DEBIAN_FRONTEND=noninteractive SHELL ["/usr/bin/env", "bash", "-euo", "pipefail", "-c"] RUN < Date: Wed, 6 May 2026 13:55:06 -0300 Subject: [PATCH 016/113] feat: make ci always build node --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6e34e676..719806edc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -172,7 +172,6 @@ jobs: - arch: arm64 target: aarch64-unknown-linux-gnu os: ubuntu-24.04-arm - if: startsWith(github.ref, 'refs/tags/v') runs-on: ${{ matrix.os }} container: image: rust:1.90-trixie From b150cfc2b4c7022acc9ea379975b308cc01df97a Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 18 May 2026 21:41:48 -0300 Subject: [PATCH 017/113] Bump rollups-contracts from 3.0.0-alpha.4 to 3.0.0-alpha.5 --- cartesi-rollups/contracts/foundry.toml | 10 +++++----- cartesi-rollups/contracts/script/Deployment.s.sol | 2 +- cartesi-rollups/contracts/script/deploy.sh | 2 +- cartesi-rollups/contracts/soldeer.lock | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index 917fcf57c..6ef9bce61 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -8,10 +8,10 @@ via_ir = true allow_paths = ["../../prt/contracts", "../../machine/step"] remappings = [ - "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/dependencies/@openzeppelin-contracts-5.2.0/", + "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/dependencies/@openzeppelin-contracts-5.2.0/", "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/", - "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/dependencies/cartesi-machine-solidity-step-0.13.0/", - "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/", + "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/dependencies/cartesi-machine-solidity-step-0.13.0/", + "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/", "forge-std-1.9.6/=dependencies/forge-std-1.9.6/", "prt-contracts/=../../prt/contracts/src/", "step/=../../machine/step/", @@ -21,7 +21,7 @@ solc_version = "0.8.30" evm_version = "prague" fs_permissions = [ { access = "read-write", path = "deployments" }, - { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.4/deployments" }, + { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/deployments" }, { access = "read", path = "../../prt/contracts/deployments" }, ] @@ -38,4 +38,4 @@ exclude_lints = ["incorrect-shift"] [dependencies] "@openzeppelin-contracts" = "5.5.0" forge-std = "1.9.6" -cartesi-rollups-contracts = "3.0.0-alpha.4" +cartesi-rollups-contracts = "3.0.0-alpha.5" diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index 345eab16f..4938f192c 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -10,7 +10,7 @@ import {DaveAppFactory} from "src/DaveAppFactory.sol"; contract DeploymentScript is BaseDeploymentScript { function run() external { _importDeployments("../../prt/contracts"); - _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.4"); + _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.5"); address inputBox = _loadDeployment(".", "InputBox"); address appFactory = _loadDeployment(".", "ApplicationFactory"); diff --git a/cartesi-rollups/contracts/script/deploy.sh b/cartesi-rollups/contracts/script/deploy.sh index 7f7114ddf..2fe69b9cb 100755 --- a/cartesi-rollups/contracts/script/deploy.sh +++ b/cartesi-rollups/contracts/script/deploy.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." roots=( '../../prt/contracts' - 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.4' + 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.5' '.' ) diff --git a/cartesi-rollups/contracts/soldeer.lock b/cartesi-rollups/contracts/soldeer.lock index 580d8781d..181b6615f 100644 --- a/cartesi-rollups/contracts/soldeer.lock +++ b/cartesi-rollups/contracts/soldeer.lock @@ -7,10 +7,10 @@ integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" [[dependencies]] name = "cartesi-rollups-contracts" -version = "3.0.0-alpha.4" -url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_4_05-05-2026_13:59:19_rollups-contracts.zip" -checksum = "7e1f938a3f026d25672d060903d3df9f7838f89a4d5b3275235cf131bde084a9" -integrity = "d3019afac0ab8e35439c19c535689ad7857ffb59e0fdd319d757e533c77427b0" +version = "3.0.0-alpha.5" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_5_19-05-2026_01:12:42_rollups-contracts.zip" +checksum = "5a1cf8a90d344a0bb4a109556b842dc97ceedc84a5ede00f6e7ce14e2c892984" +integrity = "4f3d17b92ee51d52e29c8ac57f93faa404157d1d79400a12a3af4e31e912223e" [[dependencies]] name = "forge-std" From 61c63bfd260d1a347a401cac653909b6dfe50ee7 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 18 May 2026 21:42:09 -0300 Subject: [PATCH 018/113] Distribute deterministic deployment addresses --- .github/workflows/build.yml | 13 ++++++++++++- .../contracts/script/deploy-mainnets.sh | 17 +++++++++++++++++ .../contracts/script/deploy-testnets.sh | 17 +++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100755 cartesi-rollups/contracts/script/deploy-mainnets.sh create mode 100755 cartesi-rollups/contracts/script/deploy-testnets.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 719806edc..30711900f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -308,13 +308,24 @@ jobs: env: FILEPATH: upload/cartesi-rollups-prt-${{ steps.extract_version.outputs.version }}-contract-artifacts.tar.gz + - name: Simulate deployment to supported testnets and mainnets + working-directory: ./cartesi-rollups/contracts + run: | + ./script/deploy-testnets.sh + ./script/deploy-mainnets.sh + + - name: Compress testnet and mainnet deployment simulation artifacts + run: tar -czf "$FILEPATH" -C cartesi-rollups/contracts deployments + env: + FILEPATH: upload/cartesi-rollups-prt-${{ steps.extract_version.outputs.version }}-deployment-addresses.tar.gz + - name: Build devnet working-directory: ./cartesi-rollups/contracts run: | just build-devnet - name: Compress devnet artifacts - run: tar -czf "$FILEPATH" -C cartesi-rollups/contracts deployments state.json + run: tar -czf "$FILEPATH" -C cartesi-rollups/contracts deployments/31337 state.json env: FILEPATH: upload/cartesi-rollups-prt-${{ steps.extract_version.outputs.version }}-anvil-${{ steps.setup.outputs.installed-foundry-version }}.tar.gz diff --git a/cartesi-rollups/contracts/script/deploy-mainnets.sh b/cartesi-rollups/contracts/script/deploy-mainnets.sh new file mode 100755 index 000000000..31dfbebc4 --- /dev/null +++ b/cartesi-rollups/contracts/script/deploy-mainnets.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "${BASH_SOURCE%/*}/.." + +chain_ids=( + 1 # Ethereum Mainnet + 10 # OP Mainnet + 8453 # Base Mainnet + 42161 # Arbitrum Mainnet +) + +for chain_id in "${chain_ids[@]}" +do + ./script/deploy.sh --chain-id "$chain_id" "$@" +done diff --git a/cartesi-rollups/contracts/script/deploy-testnets.sh b/cartesi-rollups/contracts/script/deploy-testnets.sh new file mode 100755 index 000000000..5069d3908 --- /dev/null +++ b/cartesi-rollups/contracts/script/deploy-testnets.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "${BASH_SOURCE%/*}/.." + +chain_ids=( + 84532 # Base Sepolia + 421614 # Arbitrum Sepolia + 11155111 # Ethereum Sepolia + 11155420 # OP Sepolia +) + +for chain_id in "${chain_ids[@]}" +do + ./script/deploy.sh --chain-id "$chain_id" "$@" +done From 59d2708f7e5864de4292cabe30d0520aa4144b72 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 18 May 2026 22:04:51 -0300 Subject: [PATCH 019/113] Dry-run contract artifacts release job --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30711900f..ec30f187e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -271,7 +271,6 @@ jobs: release-contracts: needs: [prt-contracts, dave-contracts, prt-honeypot, build] runs-on: ubuntu-24.04 - if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 with: @@ -283,7 +282,12 @@ jobs: - name: Extract version from tag id: extract_version - run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + run: | + if [[ "$GITHUB_REF" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + else + echo "version=0.0.0-dev" >> $GITHUB_OUTPUT + fi - name: Setup tools uses: ./.github/actions/setup-tools @@ -330,6 +334,7 @@ jobs: FILEPATH: upload/cartesi-rollups-prt-${{ steps.extract_version.outputs.version }}-anvil-${{ steps.setup.outputs.installed-foundry-version }}.tar.gz - name: Upload assets to release on GitHub + if: startsWith(github.ref, 'refs/tags/v') run: gh release upload "$TAG" upload/* --clobber env: GH_TOKEN: ${{ github.token }} From 1fccdb33a336fe0d13a39efea67b70ca85031e04 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Thu, 21 May 2026 09:19:21 -0300 Subject: [PATCH 020/113] feat: bump rollups-contracts from 3.0.0-alpha.5 to 3.0.0-alpha.6 --- cartesi-rollups/contracts/foundry.toml | 10 +++++----- cartesi-rollups/contracts/script/Deployment.s.sol | 2 +- cartesi-rollups/contracts/script/deploy.sh | 2 +- cartesi-rollups/contracts/soldeer.lock | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index 6ef9bce61..febc75f78 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -8,10 +8,10 @@ via_ir = true allow_paths = ["../../prt/contracts", "../../machine/step"] remappings = [ - "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/dependencies/@openzeppelin-contracts-5.2.0/", + "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/dependencies/@openzeppelin-contracts-5.2.0/", "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/", - "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/dependencies/cartesi-machine-solidity-step-0.13.0/", - "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/", + "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/dependencies/cartesi-machine-solidity-step-0.13.0/", + "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/", "forge-std-1.9.6/=dependencies/forge-std-1.9.6/", "prt-contracts/=../../prt/contracts/src/", "step/=../../machine/step/", @@ -21,7 +21,7 @@ solc_version = "0.8.30" evm_version = "prague" fs_permissions = [ { access = "read-write", path = "deployments" }, - { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.5/deployments" }, + { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/deployments" }, { access = "read", path = "../../prt/contracts/deployments" }, ] @@ -38,4 +38,4 @@ exclude_lints = ["incorrect-shift"] [dependencies] "@openzeppelin-contracts" = "5.5.0" forge-std = "1.9.6" -cartesi-rollups-contracts = "3.0.0-alpha.5" +cartesi-rollups-contracts = "3.0.0-alpha.6" diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index 4938f192c..a5774a4dc 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -10,7 +10,7 @@ import {DaveAppFactory} from "src/DaveAppFactory.sol"; contract DeploymentScript is BaseDeploymentScript { function run() external { _importDeployments("../../prt/contracts"); - _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.5"); + _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.6"); address inputBox = _loadDeployment(".", "InputBox"); address appFactory = _loadDeployment(".", "ApplicationFactory"); diff --git a/cartesi-rollups/contracts/script/deploy.sh b/cartesi-rollups/contracts/script/deploy.sh index 2fe69b9cb..843d5f702 100755 --- a/cartesi-rollups/contracts/script/deploy.sh +++ b/cartesi-rollups/contracts/script/deploy.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." roots=( '../../prt/contracts' - 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.5' + 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.6' '.' ) diff --git a/cartesi-rollups/contracts/soldeer.lock b/cartesi-rollups/contracts/soldeer.lock index 181b6615f..7e7a34fcd 100644 --- a/cartesi-rollups/contracts/soldeer.lock +++ b/cartesi-rollups/contracts/soldeer.lock @@ -7,10 +7,10 @@ integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" [[dependencies]] name = "cartesi-rollups-contracts" -version = "3.0.0-alpha.5" -url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_5_19-05-2026_01:12:42_rollups-contracts.zip" -checksum = "5a1cf8a90d344a0bb4a109556b842dc97ceedc84a5ede00f6e7ce14e2c892984" -integrity = "4f3d17b92ee51d52e29c8ac57f93faa404157d1d79400a12a3af4e31e912223e" +version = "3.0.0-alpha.6" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_6_21-05-2026_12:17:44_rollups-contracts.zip" +checksum = "cdfd86f90895ba37e188103e8fb62d6ca8e45a53846ebc63cb7b7af66f18d034" +integrity = "6fd07e7d895795aca4877d1c0f357f19f88adc56e1ff1ff88950900391328969" [[dependencies]] name = "forge-std" From fec056e7fd8f8cd93933c6cc0c5116a33dde212f Mon Sep 17 00:00:00 2001 From: Enderson Maia Date: Wed, 6 May 2026 14:34:48 -0300 Subject: [PATCH 021/113] ci: lock GH actions by digest --- .github/actions/setup-tools/action.yml | 12 ++++++------ .github/workflows/build.yml | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml index 973d64fdc..651307332 100644 --- a/.github/actions/setup-tools/action.yml +++ b/.github/actions/setup-tools/action.yml @@ -29,11 +29,11 @@ runs: using: composite steps: - name: Set up QEMU for riscv support - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 if: ${{ inputs.setup-qemu == 'true' }} - name: Install Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0 if: ${{ inputs.setup-rust == 'true' }} with: components: rustfmt @@ -49,19 +49,19 @@ runs: echo "version=${{ inputs.foundry-version }}" >> $GITHUB_OUTPUT - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 with: version: ${{ steps.set-foundry-version.outputs.version }} - name: Install just - uses: extractions/setup-just@v3 + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 with: package_json_file: 'prt/contracts/package.json' - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: 'pnpm' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec30f187e..6c47eb7c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: prt-contracts: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive @@ -36,7 +36,7 @@ jobs: dave-contracts: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive @@ -63,7 +63,7 @@ jobs: prt-honeypot: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive - name: Setup tools @@ -110,7 +110,7 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive @@ -178,7 +178,7 @@ jobs: env: DEBIAN_FRONTEND: noninteractive steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive @@ -223,7 +223,7 @@ jobs: cp -v ./target/${{ matrix.target }}/release/cartesi-rollups-prt-node cartesi-rollups-prt-node - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cartesi-rollups-prt-node-linux-${{ matrix.arch }} path: | @@ -241,7 +241,7 @@ jobs: - arch: arm64 if: startsWith(github.ref, 'refs/tags/v') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Create directory run: mkdir -p upload @@ -250,7 +250,7 @@ jobs: id: extract_version run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cartesi-rollups-prt-node-linux-${{ matrix.arch }} @@ -272,7 +272,7 @@ jobs: needs: [prt-contracts, dave-contracts, prt-honeypot, build] runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive From 56f50042c8496de0ff95618232209450c8d25186 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Fri, 19 Jun 2026 19:03:25 -0300 Subject: [PATCH 022/113] refactor: remove dependency from Cannon and pnpm --- .github/actions/setup-tools/action.yml | 11 - README.md | 1 - cartesi-rollups/contracts/.gitignore | 2 +- cartesi-rollups/contracts/.soldeerignore | 2 - cartesi-rollups/contracts/README.md | 9 +- cartesi-rollups/contracts/cannonfile.toml | 20 - cartesi-rollups/contracts/justfile | 13 +- cartesi-rollups/contracts/package.json | 9 - cartesi-rollups/contracts/pnpm-lock.yaml | 2163 ----------------- .../node/cartesi-rollups-prt-node/src/args.rs | 4 +- prt/client-rs/core/src/tournament/config.rs | 4 +- prt/contracts/.gitignore | 2 +- prt/contracts/README.md | 13 +- prt/contracts/cannonfile.toml | 72 - prt/contracts/justfile | 9 +- prt/contracts/package.json | 9 - prt/contracts/pnpm-lock.yaml | 2163 ----------------- test/Dockerfile | 30 +- 18 files changed, 15 insertions(+), 4521 deletions(-) delete mode 100644 cartesi-rollups/contracts/cannonfile.toml delete mode 100644 cartesi-rollups/contracts/package.json delete mode 100644 cartesi-rollups/contracts/pnpm-lock.yaml delete mode 100644 prt/contracts/cannonfile.toml delete mode 100644 prt/contracts/package.json delete mode 100644 prt/contracts/pnpm-lock.yaml diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml index 651307332..e6e41e16b 100644 --- a/.github/actions/setup-tools/action.yml +++ b/.github/actions/setup-tools/action.yml @@ -55,14 +55,3 @@ runs: - name: Install just uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0 - - - name: Install pnpm - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - with: - package_json_file: 'prt/contracts/package.json' - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 22 - cache: 'pnpm' - cache-dependency-path: 'prt/contracts/pnpm-lock.yaml' diff --git a/README.md b/README.md index ea95500eb..806fba9c0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ Choose the setup that best fits your workflow. - git - Docker -- pnpm - just - GNU make - [foundry](https://github.com/foundry-rs/foundry) diff --git a/cartesi-rollups/contracts/.gitignore b/cartesi-rollups/contracts/.gitignore index 87a5362d9..8d6b87fcf 100644 --- a/cartesi-rollups/contracts/.gitignore +++ b/cartesi-rollups/contracts/.gitignore @@ -19,7 +19,7 @@ docs/ # Gas snapshot .gas-snapshot -# Cannon +# Forge deployment script /deployments /state.json diff --git a/cartesi-rollups/contracts/.soldeerignore b/cartesi-rollups/contracts/.soldeerignore index 60ccf9562..9daeafb98 100644 --- a/cartesi-rollups/contracts/.soldeerignore +++ b/cartesi-rollups/contracts/.soldeerignore @@ -1,3 +1 @@ -package.json -pnpm-lock.yaml test diff --git a/cartesi-rollups/contracts/README.md b/cartesi-rollups/contracts/README.md index 27f7c4df5..aa059057d 100644 --- a/cartesi-rollups/contracts/README.md +++ b/cartesi-rollups/contracts/README.md @@ -9,11 +9,10 @@ This contract instantiates a PRT tournament every epoch to settle on the new sta - Integrates Dave PRT with Cartesi Rollups - Contains a factory contract for `DaveConsensus` contracts - Unit tests and deployment scripts in Solidity using Forge -- Cannonfile for modular deployments ## Installing dependencies -In order to install the Node.js and Solidity dependencies, please run the following command. +In order to install the Solidity dependencies, please run the following command. ```sh just install-deps @@ -38,10 +37,10 @@ just test ## Deploying the core contracts In order to deploy the core contracts, you may run the following command. -You may want to consult the [Cannon CLI documentation] for deployment options. +You may want to consult the [Forge script documentation] for options. ```sh -just deploy-core # [options...] +./script/deploy.sh # [options...] ``` -[Cannon CLI documentation]: https://usecannon.com/learn/cli +[Forge script documentation]: https://www.getfoundry.sh/reference/forge/script#forge-script diff --git a/cartesi-rollups/contracts/cannonfile.toml b/cartesi-rollups/contracts/cannonfile.toml deleted file mode 100644 index 7fcdf21f0..000000000 --- a/cartesi-rollups/contracts/cannonfile.toml +++ /dev/null @@ -1,20 +0,0 @@ -name = 'cartesi-dave-app-factory' -version = '2.1.1' -description = 'Cartesi Dave App Factory' - -[pull.prtContracts] -source = "cartesi-prt-multilevel:2.1.1@main" - -[pull.cartesiRollups] -source = "cartesi-rollups:2.2.0@main" - -[deploy.DaveAppFactory] -artifact = "DaveAppFactory" -args = [ - "<%= cartesiRollups.InputBox.address %>", - "<%= cartesiRollups.ApplicationFactory.address %>", - "<%= prtContracts.MultiLevelTournamentFactory.address %>", -] -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" diff --git a/cartesi-rollups/contracts/justfile b/cartesi-rollups/contracts/justfile index 3f1e17ef6..696d5d759 100644 --- a/cartesi-rollups/contracts/justfile +++ b/cartesi-rollups/contracts/justfile @@ -16,8 +16,7 @@ fmt: check-fmt: forge fmt --check -install-deps PNPM_INSTALL_ARGS='' PNPM_CI='true': - CI={{PNPM_CI}} pnpm install {{PNPM_INSTALL_ARGS}} +install-deps: forge soldeer install # compile smart contracts @@ -53,13 +52,3 @@ bind: clean-bindings build-devnet: ./script/build-devnet.sh - -deploy-core *OPTS: \ - (deploy-prt-core OPTS) \ - (deploy "cannonfile.toml" OPTS) - -deploy-prt-core *OPTS: - just -f ../../prt/contracts/justfile deploy-core {{OPTS}} - -deploy CANNONFILE *OPTS: - pnpm cannon build {{CANNONFILE}} {{OPTS}} diff --git a/cartesi-rollups/contracts/package.json b/cartesi-rollups/contracts/package.json deleted file mode 100644 index f33e1c8b0..000000000 --- a/cartesi-rollups/contracts/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@cartesi/dave-consensus", - "version": "0.0.1", - "license": "Apache-2.0", - "packageManager": "pnpm@10.7.0", - "dependencies": { - "@usecannon/cli": "^2.25.1" - } -} diff --git a/cartesi-rollups/contracts/pnpm-lock.yaml b/cartesi-rollups/contracts/pnpm-lock.yaml deleted file mode 100644 index d2a2f7252..000000000 --- a/cartesi-rollups/contracts/pnpm-lock.yaml +++ /dev/null @@ -1,2163 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@usecannon/cli': - specifier: ^2.25.1 - version: 2.25.1 - -packages: - - '@adraffy/ens-normalize@1.11.1': - resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} - - '@assemblyscript/loader@0.9.4': - resolution: {integrity: sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@endo/cache-map@1.1.0': - resolution: {integrity: sha512-owFGshs/97PDw9oguZqU/px8Lv1d0KjAUtDUiPwKHNXRVUE/jyettEbRoTbNJR1OaI8biMn6bHr9kVJsOh6dXw==} - - '@endo/env-options@1.1.11': - resolution: {integrity: sha512-p9OnAPsdqoX4YJsE98e3NBVhIr2iW9gNZxHhAI2/Ul5TdRfoOViItzHzTqrgUVopw6XxA1u1uS6CykLMDUxarA==} - - '@endo/immutable-arraybuffer@1.1.2': - resolution: {integrity: sha512-u+NaYB2aqEugQ3u7w3c5QNkPogf8q/xGgsPaqdY6pUiGWtYiTiFspKFcha6+oeZhWXWQ23rf0KrUq0kfuzqYyQ==} - - '@ethersproject/abi@5.8.0': - resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - - '@ethersproject/abstract-provider@5.8.0': - resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - - '@ethersproject/abstract-signer@5.8.0': - resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - - '@ethersproject/address@5.8.0': - resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - - '@ethersproject/base64@5.8.0': - resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - - '@ethersproject/bignumber@5.8.0': - resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - - '@ethersproject/bytes@5.8.0': - resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - - '@ethersproject/constants@5.8.0': - resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - - '@ethersproject/hash@5.8.0': - resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - - '@ethersproject/keccak256@5.8.0': - resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - - '@ethersproject/logger@5.8.0': - resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - - '@ethersproject/networks@5.8.0': - resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - - '@ethersproject/properties@5.8.0': - resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - - '@ethersproject/rlp@5.8.0': - resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - - '@ethersproject/signing-key@5.8.0': - resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - - '@ethersproject/strings@5.8.0': - resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - - '@ethersproject/transactions@5.8.0': - resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - - '@ethersproject/web@5.8.0': - resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - - '@iarna/toml@3.0.0': - resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@multiformats/base-x@4.0.1': - resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} - - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.1': - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/bip32@1.7.0': - resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - - '@scure/bip39@1.6.0': - resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} - - '@types/long@4.0.2': - resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} - - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - - '@types/node@24.9.2': - resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@usecannon/builder@2.25.1': - resolution: {integrity: sha512-fDhFNQeeO7D0FiTWaU/86U7OfMhvcHIAhHXPUZ4tNx/F7t0fg0HhCTu/dniGKLWb9PqIiSEMWaX1eX5Pga2vTw==} - engines: {node: '>=16.0.0'} - - '@usecannon/cli@2.25.1': - resolution: {integrity: sha512-O8kRQtUA/7bI0iUcsKZhkl945kHr/3a2VVN0wDzbjvzBMfwOkobDHescqPS48rCkY5EVgErZfw+UVTSVsEB2qw==} - hasBin: true - - '@usecannon/router@4.1.3': - resolution: {integrity: sha512-s6YfUovQoh1bW10LWHhrRQHUd+IK1L69daqBa+kGxMGpGwmx12LqjUdT+inB2YjtbYy7FLaLd+DV6tegIi2wXQ==} - - '@usecannon/web-solc@0.5.1': - resolution: {integrity: sha512-wK8J1snp1ikYzpxA8LzhQwp+6cvmLDnFG2EaMLmqtQZD/FIz7xh8IaSgHUliwDBm9zdsPEIvXvhDvanyZ7IBuw==} - - abitype@1.1.0: - resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - - abitype@1.1.1: - resolution: {integrity: sha512-Loe5/6tAgsBukY95eGaPSDmQHIjRZYQq8PB1MpsNccDIK8WiV+Uw6WzaIXipvaxTEL2yEB0OpEaQv3gs8pkS9Q==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - axios-retry@4.5.0: - resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} - peerDependencies: - axios: 0.x || 1.x - - axios@1.13.1: - resolution: {integrity: sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - - blakejs@1.2.1: - resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} - - bn.js@4.12.2: - resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} - - bn.js@5.2.2: - resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - cids@1.1.9: - resolution: {integrity: sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg==} - engines: {node: '>=4.0.0', npm: '>=3.0.0'} - deprecated: This module has been superseded by the multiformats module - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - deep-freeze@0.0.1: - resolution: {integrity: sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - elliptic@6.6.1: - resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - err-code@3.0.1: - resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - eth-provider@0.13.7: - resolution: {integrity: sha512-D07HcKBQ0+liERDbkwpex03Y5D7agOMBv8NMkGu0obmD+vHzP9q8jI/tkZMfYAhbfXwpudEgXKiJODXH5UQu7g==} - - ethereum-provider@0.7.7: - resolution: {integrity: sha512-ulbjKgu1p2IqtZqNTNfzXysvFJrMR3oTmWEEX3DnoEae7WLd4MkY4u82kvXhxA2C171rK8IVlcodENX7TXvHTA==} - - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} - engines: {node: '>= 6'} - - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} - engines: {node: '>=14.14'} - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - fuse.js@7.1.0: - resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} - engines: {node: '>=10'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - hasBin: true - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hamt-sharding@2.0.1: - resolution: {integrity: sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA==} - engines: {node: '>=10.0.0', npm: '>=6.0.0'} - - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - http-https@1.0.0: - resolution: {integrity: sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - interface-ipld-format@1.0.1: - resolution: {integrity: sha512-WV/ar+KQJVoQpqRDYdo7YPGYIUHJxCuOEhdvsRpzLqoOIVCqPKdMMYmsLL1nCRsF3yYNio+PAJbCKiv6drrEAg==} - deprecated: This module has been superseded by the multiformats module - - ipfs-only-hash@4.0.0: - resolution: {integrity: sha512-TE1DZCvfw8i3gcsTq3P4TFx3cKFJ3sluu/J3XINkJhIN9OwJgNMqKA+WnKx6ByCb1IoPXsTp1KM7tupElb6SyA==} - hasBin: true - - ipfs-unixfs-importer@7.0.3: - resolution: {integrity: sha512-qeFOlD3AQtGzr90sr5Tq1Bi8pT5Nr2tSI8z310m7R4JDYgZc6J1PEZO3XZQ8l1kuGoqlAppBZuOYmPEqaHcVQQ==} - engines: {node: '>=14.0.0', npm: '>=7.0.0'} - - ipfs-unixfs@4.0.3: - resolution: {integrity: sha512-hzJ3X4vlKT8FQ3Xc4M1szaFVjsc1ZydN+E4VQ91aXxfpjFn9G2wsMo1EFdAXNq/BUnN5dgqIOMP5zRYr3DTsAw==} - engines: {node: '>=14.0.0', npm: '>=7.0.0'} - - ipld-dag-pb@0.22.3: - resolution: {integrity: sha512-dfG5C5OVAR4FEP7Al2CrHWvAyIM7UhAQrjnOYOIxXGQz5NlEj6wGX0XQf6Ru6or1na6upvV3NQfstapQG8X2rg==} - engines: {node: '>=6.0.0', npm: '>=3.0.0'} - deprecated: This module has been superseded by @ipld/dag-pb and multiformats - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - - is-retry-allowed@2.2.0: - resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} - engines: {node: '>=10'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isows@1.0.7: - resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} - peerDependencies: - ws: '*' - - it-all@1.0.6: - resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} - - it-batch@1.0.9: - resolution: {integrity: sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==} - - it-first@1.0.7: - resolution: {integrity: sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==} - - it-parallel-batch@1.0.11: - resolution: {integrity: sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==} - - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} - engines: {node: 20 || >=22} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - meow@9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - - merge-options@3.0.4: - resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} - engines: {node: '>=10'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} - - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multibase@4.0.6: - resolution: {integrity: sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - deprecated: This module has been superseded by the multiformats module - - multicodec@3.2.1: - resolution: {integrity: sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==} - deprecated: This module has been superseded by the multiformats module - - multiformats@9.9.0: - resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} - - multihashes@4.0.3: - resolution: {integrity: sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - - multihashing-async@2.1.4: - resolution: {integrity: sha512-sB1MiQXPSBTNRVSJc2zM157PXgDtud2nMFUEIvBrsq5Wv96sUclMRK/ecjoP1T/W61UJBqt4tCTwMkUpt2Gbzg==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - - murmurhash3js-revisited@3.0.0: - resolution: {integrity: sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==} - engines: {node: '>=8.0.0'} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - nanospinner@1.2.2: - resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - oboe@2.1.5: - resolution: {integrity: sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==} - - ox@0.9.6: - resolution: {integrity: sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} - engines: {node: 20 || >=22} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - promise-events@0.2.4: - resolution: {integrity: sha512-GCM6DmJcSCC8XboZIzYJAlADwkIS1P54XFUJQYhB7dpE7rtXPzPrT13dsV4Qm0FMCKptwMTyF8ZCir803RfKzA==} - engines: {node: '>=8.0.0'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - protobufjs@6.11.4: - resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} - hasBin: true - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - - rabin-wasm@0.1.5: - resolution: {integrity: sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==} - hasBin: true - - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - ses@1.14.0: - resolution: {integrity: sha512-T07hNgOfVRTLZGwSS50RnhqrG3foWP+rM+Q5Du4KUQyMLFI3A8YA4RKl0jjZzhihC1ZvDGrWi/JMn4vqbgr/Jg==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - - sparse-array@1.3.2: - resolution: {integrity: sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} - - tildify@3.0.0: - resolution: {integrity: sha512-9ZLMl75qnTLr7oSEmWJbKemFS/fP4TMBiF6PFwGwLpgobebU1ehXoGbadJ+7jT8fjaz2G82JgN9G4taz+o1j1w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - typestub-ipfs-only-hash@4.0.0: - resolution: {integrity: sha512-HKLePX0XiPiyqoueSfvCLL9SIzvKBXjASaRoR0yk/gUbbK7cqejU6/tjhihwmzBCvWbx5aMQ2LYsYIpMK7Ikpg==} - - uint8arrays@2.1.10: - resolution: {integrity: sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==} - - uint8arrays@3.1.1: - resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - varint@5.0.2: - resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} - - varint@6.0.0: - resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} - - viem@2.38.5: - resolution: {integrity: sha512-EU2olUnWd5kBK1t3BicwaamPHGUANRYetoDLSVzDy7XQ8o8UswItnkQbufe3xTcdRCtb2JYMwjlgHZZ7fUoLdA==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.9.0: - resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xhr2-cookies@1.1.0: - resolution: {integrity: sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - znv@0.4.0: - resolution: {integrity: sha512-6/pGsQhBisLzKdyC90mUCRgYDtCfQ4aQ68sDybexq3GMzqqkp662GH6qIyuCHJC1i72hJPHbWAhccTJVuZUQfA==} - peerDependencies: - zod: ^3.13.2 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@adraffy/ens-normalize@1.11.1': {} - - '@assemblyscript/loader@0.9.4': {} - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-validator-identifier@7.28.5': {} - - '@endo/cache-map@1.1.0': {} - - '@endo/env-options@1.1.11': {} - - '@endo/immutable-arraybuffer@1.1.2': {} - - '@ethersproject/abi@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/abstract-provider@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - - '@ethersproject/abstract-signer@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/address@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/rlp': 5.8.0 - - '@ethersproject/base64@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - - '@ethersproject/bignumber@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - bn.js: 5.2.2 - - '@ethersproject/bytes@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/constants@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - - '@ethersproject/hash@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/keccak256@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - js-sha3: 0.8.0 - - '@ethersproject/logger@5.8.0': {} - - '@ethersproject/networks@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/properties@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/rlp@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/signing-key@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - bn.js: 5.2.2 - elliptic: 6.6.1 - hash.js: 1.1.7 - - '@ethersproject/strings@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/transactions@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - - '@ethersproject/web@5.8.0': - dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@iarna/toml@3.0.0': {} - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@multiformats/base-x@4.0.1': {} - - '@noble/ciphers@1.3.0': {} - - '@noble/curves@1.9.1': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.8.0': {} - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.4': {} - - '@protobufjs/eventemitter@1.1.0': {} - - '@protobufjs/fetch@1.1.0': - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - - '@protobufjs/float@1.0.2': {} - - '@protobufjs/inquire@1.1.0': {} - - '@protobufjs/path@1.1.2': {} - - '@protobufjs/pool@1.1.0': {} - - '@protobufjs/utf8@1.1.0': {} - - '@scure/base@1.2.6': {} - - '@scure/bip32@1.7.0': - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/bip39@1.6.0': - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@types/long@4.0.2': {} - - '@types/minimist@1.2.5': {} - - '@types/node@24.9.2': - dependencies: - undici-types: 7.16.0 - - '@types/normalize-package-data@2.4.4': {} - - '@usecannon/builder@2.25.1': - dependencies: - '@usecannon/router': 4.1.3 - '@usecannon/web-solc': 0.5.1 - acorn: 8.15.0 - axios: 1.13.1(debug@4.4.3) - axios-retry: 4.5.0(axios@1.13.1(debug@4.4.3)) - buffer: 6.0.3 - chalk: 4.1.2 - debug: 4.4.3 - deep-freeze: 0.0.1 - form-data: 4.0.4 - fuse.js: 7.1.0 - lodash: 4.17.21 - pako: 2.1.0 - promise-events: 0.2.4 - rfdc: 1.4.1 - ses: 1.14.0 - typestub-ipfs-only-hash: 4.0.0 - viem: 2.38.5(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - - '@usecannon/cli@2.25.1': - dependencies: - '@iarna/toml': 3.0.0 - '@usecannon/builder': 2.25.1 - abitype: 1.1.1(zod@3.25.76) - chalk: 4.1.2 - commander: 12.1.0 - debug: 4.4.3 - eth-provider: 0.13.7 - fs-extra: 11.3.2 - glob: 11.0.3 - lodash: 4.17.21 - nanospinner: 1.2.2 - prompts: 2.4.2 - semver: 7.7.3 - table: 6.9.0 - tildify: 3.0.0 - untildify: 4.0.0 - viem: 2.38.5(zod@3.25.76) - znv: 0.4.0(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - - '@usecannon/router@4.1.3': - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - debug: 4.4.3 - mustache: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@usecannon/web-solc@0.5.1': {} - - abitype@1.1.0(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - abitype@1.1.1(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - acorn@8.15.0: {} - - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - arrify@1.0.1: {} - - astral-regex@2.0.0: {} - - asynckit@0.4.0: {} - - axios-retry@4.5.0(axios@1.13.1(debug@4.4.3)): - dependencies: - axios: 1.13.1(debug@4.4.3) - is-retry-allowed: 2.2.0 - - axios@1.13.1(debug@4.4.3): - dependencies: - follow-redirects: 1.15.11(debug@4.4.3) - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - base64-js@1.5.1: {} - - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.2 - - blakejs@1.2.1: {} - - bn.js@4.12.2: {} - - bn.js@5.2.2: {} - - brorand@1.1.0: {} - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - - camelcase@5.3.1: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - cids@1.1.9: - dependencies: - multibase: 4.0.6 - multicodec: 3.2.1 - multihashes: 4.0.3 - uint8arrays: 3.1.1 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colorette@2.0.20: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@12.1.0: {} - - cookiejar@2.1.4: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - - deep-freeze@0.0.1: {} - - delayed-stream@1.0.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - elliptic@6.6.1: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - err-code@3.0.1: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - eth-provider@0.13.7: - dependencies: - ethereum-provider: 0.7.7 - events: 3.3.0 - oboe: 2.1.5 - uuid: 9.0.0 - ws: 8.9.0 - xhr2-cookies: 1.1.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - ethereum-provider@0.7.7: - dependencies: - events: 3.3.0 - - eventemitter3@5.0.1: {} - - events@3.3.0: {} - - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.0: {} - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - follow-redirects@1.15.11(debug@4.4.3): - optionalDependencies: - debug: 4.4.3 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - fs-extra@11.3.2: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - function-bind@1.1.2: {} - - fuse.js@7.1.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - glob@11.0.3: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - hamt-sharding@2.0.1: - dependencies: - sparse-array: 1.3.2 - uint8arrays: 3.1.1 - - hard-rejection@2.1.0: {} - - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - hosted-git-info@2.8.9: {} - - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - - http-https@1.0.0: {} - - ieee754@1.2.1: {} - - indent-string@4.0.0: {} - - inherits@2.0.4: {} - - interface-ipld-format@1.0.1: - dependencies: - cids: 1.1.9 - multicodec: 3.2.1 - multihashes: 4.0.3 - - ipfs-only-hash@4.0.0: - dependencies: - ipfs-unixfs-importer: 7.0.3 - meow: 9.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - ipfs-unixfs-importer@7.0.3: - dependencies: - bl: 5.1.0 - cids: 1.1.9 - err-code: 3.0.1 - hamt-sharding: 2.0.1 - ipfs-unixfs: 4.0.3 - ipld-dag-pb: 0.22.3 - it-all: 1.0.6 - it-batch: 1.0.9 - it-first: 1.0.7 - it-parallel-batch: 1.0.11 - merge-options: 3.0.4 - multihashing-async: 2.1.4 - rabin-wasm: 0.1.5 - uint8arrays: 2.1.10 - transitivePeerDependencies: - - encoding - - supports-color - - ipfs-unixfs@4.0.3: - dependencies: - err-code: 3.0.1 - protobufjs: 6.11.4 - - ipld-dag-pb@0.22.3: - dependencies: - cids: 1.1.9 - interface-ipld-format: 1.0.1 - multicodec: 3.2.1 - multihashing-async: 2.1.4 - protobufjs: 6.11.4 - stable: 0.1.8 - uint8arrays: 2.1.10 - - is-arrayish@0.2.1: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-fullwidth-code-point@3.0.0: {} - - is-plain-obj@1.1.0: {} - - is-plain-obj@2.1.0: {} - - is-retry-allowed@2.2.0: {} - - isexe@2.0.0: {} - - isows@1.0.7(ws@8.18.3): - dependencies: - ws: 8.18.3 - - it-all@1.0.6: {} - - it-batch@1.0.9: {} - - it-first@1.0.7: {} - - it-parallel-batch@1.0.11: - dependencies: - it-batch: 1.0.9 - - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - - js-sha3@0.8.0: {} - - js-tokens@4.0.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-traverse@1.0.0: {} - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - kind-of@6.0.3: {} - - kleur@3.0.3: {} - - lines-and-columns@1.2.4: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - lodash.truncate@4.4.2: {} - - lodash@4.17.21: {} - - long@4.0.0: {} - - lru-cache@11.2.2: {} - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - - math-intrinsics@1.1.0: {} - - meow@9.0.0: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - - merge-options@3.0.4: - dependencies: - is-plain-obj: 2.1.0 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - min-indent@1.0.1: {} - - minimalistic-assert@1.0.1: {} - - minimalistic-crypto-utils@1.0.1: {} - - minimatch@10.1.1: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - - minimist@1.2.8: {} - - minipass@7.1.2: {} - - ms@2.1.3: {} - - multibase@4.0.6: - dependencies: - '@multiformats/base-x': 4.0.1 - - multicodec@3.2.1: - dependencies: - uint8arrays: 3.1.1 - varint: 6.0.0 - - multiformats@9.9.0: {} - - multihashes@4.0.3: - dependencies: - multibase: 4.0.6 - uint8arrays: 3.1.1 - varint: 5.0.2 - - multihashing-async@2.1.4: - dependencies: - blakejs: 1.2.1 - err-code: 3.0.1 - js-sha3: 0.8.0 - multihashes: 4.0.3 - murmurhash3js-revisited: 3.0.0 - uint8arrays: 3.1.1 - - murmurhash3js-revisited@3.0.0: {} - - mustache@4.2.0: {} - - nanospinner@1.2.2: - dependencies: - picocolors: 1.1.1 - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.11 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 - - oboe@2.1.5: - dependencies: - http-https: 1.0.0 - - ox@0.9.6(zod@3.25.76): - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.1(zod@3.25.76) - eventemitter3: 5.0.1 - transitivePeerDependencies: - - zod - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - package-json-from-dist@1.0.1: {} - - pako@2.1.0: {} - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@2.0.0: - dependencies: - lru-cache: 11.2.2 - minipass: 7.1.2 - - picocolors@1.1.1: {} - - promise-events@0.2.4: {} - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - protobufjs@6.11.4: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/long': 4.0.2 - '@types/node': 24.9.2 - long: 4.0.0 - - proxy-from-env@1.1.0: {} - - quick-lru@4.0.1: {} - - rabin-wasm@0.1.5: - dependencies: - '@assemblyscript/loader': 0.9.4 - bl: 5.1.0 - debug: 4.4.3 - minimist: 1.2.8 - node-fetch: 2.7.0 - readable-stream: 3.6.2 - transitivePeerDependencies: - - encoding - - supports-color - - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - - require-from-string@2.0.2: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rfdc@1.4.1: {} - - safe-buffer@5.2.1: {} - - semver@5.7.2: {} - - semver@7.7.3: {} - - ses@1.14.0: - dependencies: - '@endo/cache-map': 1.1.0 - '@endo/env-options': 1.1.11 - '@endo/immutable-arraybuffer': 1.1.2 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - - sisteransi@1.0.5: {} - - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - - sparse-array@1.3.2: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 - - spdx-license-ids@3.0.22: {} - - stable@0.1.8: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - table@6.9.0: - dependencies: - ajv: 8.17.1 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - tildify@3.0.0: {} - - tr46@0.0.3: {} - - trim-newlines@3.0.1: {} - - type-fest@0.18.1: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - typestub-ipfs-only-hash@4.0.0: - dependencies: - ipfs-only-hash: 4.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - uint8arrays@2.1.10: - dependencies: - multiformats: 9.9.0 - - uint8arrays@3.1.1: - dependencies: - multiformats: 9.9.0 - - undici-types@7.16.0: {} - - universalify@2.0.1: {} - - untildify@4.0.0: {} - - util-deprecate@1.0.2: {} - - uuid@9.0.0: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - varint@5.0.2: {} - - varint@6.0.0: {} - - viem@2.38.5(zod@3.25.76): - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(zod@3.25.76) - isows: 1.0.7(ws@8.18.3) - ox: 0.9.6(zod@3.25.76) - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - - ws@8.18.3: {} - - ws@8.9.0: {} - - xhr2-cookies@1.1.0: - dependencies: - cookiejar: 2.1.4 - - yallist@4.0.0: {} - - yargs-parser@20.2.9: {} - - znv@0.4.0(zod@3.25.76): - dependencies: - colorette: 2.0.20 - zod: 3.25.76 - - zod@3.25.76: {} diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs b/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs index fc6d29b08..3f6492b55 100644 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs +++ b/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs @@ -12,7 +12,7 @@ use std::{fmt, path::PathBuf, time::Duration}; use crate::provider::create_provider; -const CANNON_CHAIN_ID: u64 = 31337; +const ANVIL_CHAIN_ID: u64 = 31337; const ANVIL_URL: &str = "http://127.0.0.1:8545"; const SLEEP_DURATION: u64 = 30; @@ -33,7 +33,7 @@ pub struct PRTArgs { pub web3_rpc_url: Url, /// blockchain chain id - #[arg(long, env, default_value_t = CANNON_CHAIN_ID)] + #[arg(long, env, default_value_t = ANVIL_CHAIN_ID)] pub web3_chain_id: u64, #[clap(subcommand)] diff --git a/prt/client-rs/core/src/tournament/config.rs b/prt/client-rs/core/src/tournament/config.rs index b38347a5b..e1d46df85 100644 --- a/prt/client-rs/core/src/tournament/config.rs +++ b/prt/client-rs/core/src/tournament/config.rs @@ -7,7 +7,7 @@ use alloy::{ }; use clap::{ArgGroup, Args, Parser}; -const CANNON_CHAIN_ID: u64 = 31337; +const ANVIL_CHAIN_ID: u64 = 31337; const ANVIL_URL: &str = "http://127.0.0.1:8545"; pub const ANVIL_KEY_1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; @@ -22,7 +22,7 @@ pub struct BlockchainConfig { #[arg(long, env, default_value = ANVIL_URL)] pub web3_rpc_url: String, /// chain id of the blockchain - #[arg(long, env, default_value_t = CANNON_CHAIN_ID)] + #[arg(long, env, default_value_t = ANVIL_CHAIN_ID)] pub web3_chain_id: u64, /// private key of player's wallet #[arg(long, env, group = "auth")] diff --git a/prt/contracts/.gitignore b/prt/contracts/.gitignore index 7442fb6b0..92bdddb2f 100644 --- a/prt/contracts/.gitignore +++ b/prt/contracts/.gitignore @@ -24,6 +24,6 @@ lcov.info.pruned # Gas snapshot .gas-snapshot -# Cannon +# Forge deployment script /deployments /state.json diff --git a/prt/contracts/README.md b/prt/contracts/README.md index c119180d6..ce2f8cc21 100644 --- a/prt/contracts/README.md +++ b/prt/contracts/README.md @@ -13,7 +13,7 @@ which spawns and eventually resolves with a result. ## Installing dependencies -In order to install the Node.js and Solidity dependencies, please run the following command. +In order to install the Solidity dependencies, please run the following command. ```sh just install-deps @@ -34,14 +34,3 @@ You can run the unit tests with the following command. ```sh just test-all ``` - -## Deploying the core contracts - -In order to deploy the core contracts, you may run the following command. -You may want to consult the [Cannon CLI documentation] for deployment options. - -```sh -just deploy-core # [options...] -``` - -[Cannon CLI documentation]: https://usecannon.com/learn/cli diff --git a/prt/contracts/cannonfile.toml b/prt/contracts/cannonfile.toml deleted file mode 100644 index e0f5b03b1..000000000 --- a/prt/contracts/cannonfile.toml +++ /dev/null @@ -1,72 +0,0 @@ -name = 'cartesi-prt-multilevel' -version = '2.1.1' -description = 'Cartesi PRT contracts' - -[var.MainnetTournamentConstants] -mainnetMatchEffort = "<%= 60 * 5 * 92 %>" -mainnetMaxAllowance = "<%= 60 * 60 * 24 * 7 + 60 * 60 %>" - -[var.TestnetTournamentConstants] -testnetMatchEffort = "<%= 60 * 5 * 92 %>" -testnetMaxAllowance = "<%= 60 * 60 * 8 + 60 * 60 %>" - -[var.DevnetTournamentConstants] -devnetMatchEffort = "<%= 60 * 5 * 92 %>" -devnetMaxAllowance = "<%= 60 * 60 %>" - -[var.Chain] -chainType = "<%= ({1: 'main', 10: 'main', 8453: 'main', 13370: 'dev', 31337: 'dev', 42161: 'main', 84532: 'test', 421614: 'test', 11155111: 'test', 11155420: 'test'})[chainId] %>" -chainAvgBlockTime = "<%= ({1: 12, 10: 2, 8453: 2, 13370: 12, 31337: 12, 42161: 2.5, 84532: 2, 421614: 2.5, 11155111: 12, 11155420: 2})[chainId] %>" - -[var.TournamentParameters] -matchEffort = "<%= BigInt({main: settings.mainnetMatchEffort, test: settings.testnetMatchEffort, dev: settings.devnetMatchEffort}[settings.chainType] / settings.chainAvgBlockTime) %>" -maxAllowance = "<%= BigInt({main: settings.mainnetMaxAllowance, test: settings.testnetMaxAllowance, dev: settings.devnetMaxAllowance}[settings.chainType] / settings.chainAvgBlockTime) %>" - -[deploy.RiscVStateTransition] -artifact = "RiscVStateTransition" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" - -[deploy.CmioStateTransition] -artifact = "CmioStateTransition" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" - -[deploy.CartesiStateTransition] -artifact = "CartesiStateTransition" -args = [ - "<%= contracts.RiscVStateTransition.address %>", - "<%= contracts.CmioStateTransition.address %>", -] -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" - -[deploy.Tournament] -artifact = "Tournament" -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" - -[deploy.CanonicalTournamentParametersProvider] -artifact = "CanonicalTournamentParametersProvider" -args = [ - "<%= settings.matchEffort %>", - "<%= settings.maxAllowance %>", -] -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" - -[deploy.MultiLevelTournamentFactory] -artifact = "MultiLevelTournamentFactory" -args = [ - "<%= contracts.Tournament.address %>", - "<%= contracts.CanonicalTournamentParametersProvider.address %>", - "<%= contracts.CartesiStateTransition.address %>", -] -create2 = true -salt = "<%= zeroHash %>" -ifExists = "continue" diff --git a/prt/contracts/justfile b/prt/contracts/justfile index 0a7171e0c..231059d51 100644 --- a/prt/contracts/justfile +++ b/prt/contracts/justfile @@ -31,8 +31,7 @@ fmt: check-fmt: forge fmt --check -install-deps PNPM_INSTALL_ARGS='' PNPM_CI='true': - CI={{PNPM_CI}} pnpm install {{PNPM_INSTALL_ARGS}} +install-deps: forge soldeer install clean-bindings: @@ -46,9 +45,3 @@ bind: clean-bindings --module --bindings-path {{BINDINGS_DIR}} \ --skip-extra-derives \ --root {{SRC_DIR}} - -deploy-core *OPTS: \ - (deploy "cannonfile.toml" OPTS) - -deploy CANNONFILE *OPTS: - pnpm cannon build {{CANNONFILE}} {{OPTS}} diff --git a/prt/contracts/package.json b/prt/contracts/package.json deleted file mode 100644 index 6df209fc7..000000000 --- a/prt/contracts/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@cartesi/prt-contracts", - "version": "0.0.1", - "license": "Apache-2.0", - "packageManager": "pnpm@10.7.0", - "dependencies": { - "@usecannon/cli": "^2.25.1" - } -} diff --git a/prt/contracts/pnpm-lock.yaml b/prt/contracts/pnpm-lock.yaml deleted file mode 100644 index d2a2f7252..000000000 --- a/prt/contracts/pnpm-lock.yaml +++ /dev/null @@ -1,2163 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@usecannon/cli': - specifier: ^2.25.1 - version: 2.25.1 - -packages: - - '@adraffy/ens-normalize@1.11.1': - resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} - - '@assemblyscript/loader@0.9.4': - resolution: {integrity: sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@endo/cache-map@1.1.0': - resolution: {integrity: sha512-owFGshs/97PDw9oguZqU/px8Lv1d0KjAUtDUiPwKHNXRVUE/jyettEbRoTbNJR1OaI8biMn6bHr9kVJsOh6dXw==} - - '@endo/env-options@1.1.11': - resolution: {integrity: sha512-p9OnAPsdqoX4YJsE98e3NBVhIr2iW9gNZxHhAI2/Ul5TdRfoOViItzHzTqrgUVopw6XxA1u1uS6CykLMDUxarA==} - - '@endo/immutable-arraybuffer@1.1.2': - resolution: {integrity: sha512-u+NaYB2aqEugQ3u7w3c5QNkPogf8q/xGgsPaqdY6pUiGWtYiTiFspKFcha6+oeZhWXWQ23rf0KrUq0kfuzqYyQ==} - - '@ethersproject/abi@5.8.0': - resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - - '@ethersproject/abstract-provider@5.8.0': - resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - - '@ethersproject/abstract-signer@5.8.0': - resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - - '@ethersproject/address@5.8.0': - resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - - '@ethersproject/base64@5.8.0': - resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - - '@ethersproject/bignumber@5.8.0': - resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - - '@ethersproject/bytes@5.8.0': - resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - - '@ethersproject/constants@5.8.0': - resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - - '@ethersproject/hash@5.8.0': - resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - - '@ethersproject/keccak256@5.8.0': - resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - - '@ethersproject/logger@5.8.0': - resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - - '@ethersproject/networks@5.8.0': - resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - - '@ethersproject/properties@5.8.0': - resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - - '@ethersproject/rlp@5.8.0': - resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - - '@ethersproject/signing-key@5.8.0': - resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - - '@ethersproject/strings@5.8.0': - resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - - '@ethersproject/transactions@5.8.0': - resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - - '@ethersproject/web@5.8.0': - resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - - '@iarna/toml@3.0.0': - resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@multiformats/base-x@4.0.1': - resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} - - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.1': - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/bip32@1.7.0': - resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - - '@scure/bip39@1.6.0': - resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} - - '@types/long@4.0.2': - resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} - - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - - '@types/node@24.9.2': - resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@usecannon/builder@2.25.1': - resolution: {integrity: sha512-fDhFNQeeO7D0FiTWaU/86U7OfMhvcHIAhHXPUZ4tNx/F7t0fg0HhCTu/dniGKLWb9PqIiSEMWaX1eX5Pga2vTw==} - engines: {node: '>=16.0.0'} - - '@usecannon/cli@2.25.1': - resolution: {integrity: sha512-O8kRQtUA/7bI0iUcsKZhkl945kHr/3a2VVN0wDzbjvzBMfwOkobDHescqPS48rCkY5EVgErZfw+UVTSVsEB2qw==} - hasBin: true - - '@usecannon/router@4.1.3': - resolution: {integrity: sha512-s6YfUovQoh1bW10LWHhrRQHUd+IK1L69daqBa+kGxMGpGwmx12LqjUdT+inB2YjtbYy7FLaLd+DV6tegIi2wXQ==} - - '@usecannon/web-solc@0.5.1': - resolution: {integrity: sha512-wK8J1snp1ikYzpxA8LzhQwp+6cvmLDnFG2EaMLmqtQZD/FIz7xh8IaSgHUliwDBm9zdsPEIvXvhDvanyZ7IBuw==} - - abitype@1.1.0: - resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - - abitype@1.1.1: - resolution: {integrity: sha512-Loe5/6tAgsBukY95eGaPSDmQHIjRZYQq8PB1MpsNccDIK8WiV+Uw6WzaIXipvaxTEL2yEB0OpEaQv3gs8pkS9Q==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - axios-retry@4.5.0: - resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} - peerDependencies: - axios: 0.x || 1.x - - axios@1.13.1: - resolution: {integrity: sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - - blakejs@1.2.1: - resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} - - bn.js@4.12.2: - resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} - - bn.js@5.2.2: - resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - cids@1.1.9: - resolution: {integrity: sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg==} - engines: {node: '>=4.0.0', npm: '>=3.0.0'} - deprecated: This module has been superseded by the multiformats module - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - deep-freeze@0.0.1: - resolution: {integrity: sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - elliptic@6.6.1: - resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - err-code@3.0.1: - resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - eth-provider@0.13.7: - resolution: {integrity: sha512-D07HcKBQ0+liERDbkwpex03Y5D7agOMBv8NMkGu0obmD+vHzP9q8jI/tkZMfYAhbfXwpudEgXKiJODXH5UQu7g==} - - ethereum-provider@0.7.7: - resolution: {integrity: sha512-ulbjKgu1p2IqtZqNTNfzXysvFJrMR3oTmWEEX3DnoEae7WLd4MkY4u82kvXhxA2C171rK8IVlcodENX7TXvHTA==} - - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} - engines: {node: '>= 6'} - - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} - engines: {node: '>=14.14'} - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - fuse.js@7.1.0: - resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} - engines: {node: '>=10'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - hasBin: true - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hamt-sharding@2.0.1: - resolution: {integrity: sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA==} - engines: {node: '>=10.0.0', npm: '>=6.0.0'} - - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - http-https@1.0.0: - resolution: {integrity: sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - interface-ipld-format@1.0.1: - resolution: {integrity: sha512-WV/ar+KQJVoQpqRDYdo7YPGYIUHJxCuOEhdvsRpzLqoOIVCqPKdMMYmsLL1nCRsF3yYNio+PAJbCKiv6drrEAg==} - deprecated: This module has been superseded by the multiformats module - - ipfs-only-hash@4.0.0: - resolution: {integrity: sha512-TE1DZCvfw8i3gcsTq3P4TFx3cKFJ3sluu/J3XINkJhIN9OwJgNMqKA+WnKx6ByCb1IoPXsTp1KM7tupElb6SyA==} - hasBin: true - - ipfs-unixfs-importer@7.0.3: - resolution: {integrity: sha512-qeFOlD3AQtGzr90sr5Tq1Bi8pT5Nr2tSI8z310m7R4JDYgZc6J1PEZO3XZQ8l1kuGoqlAppBZuOYmPEqaHcVQQ==} - engines: {node: '>=14.0.0', npm: '>=7.0.0'} - - ipfs-unixfs@4.0.3: - resolution: {integrity: sha512-hzJ3X4vlKT8FQ3Xc4M1szaFVjsc1ZydN+E4VQ91aXxfpjFn9G2wsMo1EFdAXNq/BUnN5dgqIOMP5zRYr3DTsAw==} - engines: {node: '>=14.0.0', npm: '>=7.0.0'} - - ipld-dag-pb@0.22.3: - resolution: {integrity: sha512-dfG5C5OVAR4FEP7Al2CrHWvAyIM7UhAQrjnOYOIxXGQz5NlEj6wGX0XQf6Ru6or1na6upvV3NQfstapQG8X2rg==} - engines: {node: '>=6.0.0', npm: '>=3.0.0'} - deprecated: This module has been superseded by @ipld/dag-pb and multiformats - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - - is-retry-allowed@2.2.0: - resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} - engines: {node: '>=10'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isows@1.0.7: - resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} - peerDependencies: - ws: '*' - - it-all@1.0.6: - resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} - - it-batch@1.0.9: - resolution: {integrity: sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==} - - it-first@1.0.7: - resolution: {integrity: sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==} - - it-parallel-batch@1.0.11: - resolution: {integrity: sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==} - - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} - engines: {node: 20 || >=22} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - meow@9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - - merge-options@3.0.4: - resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} - engines: {node: '>=10'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} - - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multibase@4.0.6: - resolution: {integrity: sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - deprecated: This module has been superseded by the multiformats module - - multicodec@3.2.1: - resolution: {integrity: sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==} - deprecated: This module has been superseded by the multiformats module - - multiformats@9.9.0: - resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} - - multihashes@4.0.3: - resolution: {integrity: sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - - multihashing-async@2.1.4: - resolution: {integrity: sha512-sB1MiQXPSBTNRVSJc2zM157PXgDtud2nMFUEIvBrsq5Wv96sUclMRK/ecjoP1T/W61UJBqt4tCTwMkUpt2Gbzg==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - - murmurhash3js-revisited@3.0.0: - resolution: {integrity: sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==} - engines: {node: '>=8.0.0'} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - nanospinner@1.2.2: - resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - oboe@2.1.5: - resolution: {integrity: sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==} - - ox@0.9.6: - resolution: {integrity: sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} - engines: {node: 20 || >=22} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - promise-events@0.2.4: - resolution: {integrity: sha512-GCM6DmJcSCC8XboZIzYJAlADwkIS1P54XFUJQYhB7dpE7rtXPzPrT13dsV4Qm0FMCKptwMTyF8ZCir803RfKzA==} - engines: {node: '>=8.0.0'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - protobufjs@6.11.4: - resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} - hasBin: true - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - - rabin-wasm@0.1.5: - resolution: {integrity: sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==} - hasBin: true - - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - ses@1.14.0: - resolution: {integrity: sha512-T07hNgOfVRTLZGwSS50RnhqrG3foWP+rM+Q5Du4KUQyMLFI3A8YA4RKl0jjZzhihC1ZvDGrWi/JMn4vqbgr/Jg==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - - sparse-array@1.3.2: - resolution: {integrity: sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} - - tildify@3.0.0: - resolution: {integrity: sha512-9ZLMl75qnTLr7oSEmWJbKemFS/fP4TMBiF6PFwGwLpgobebU1ehXoGbadJ+7jT8fjaz2G82JgN9G4taz+o1j1w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - typestub-ipfs-only-hash@4.0.0: - resolution: {integrity: sha512-HKLePX0XiPiyqoueSfvCLL9SIzvKBXjASaRoR0yk/gUbbK7cqejU6/tjhihwmzBCvWbx5aMQ2LYsYIpMK7Ikpg==} - - uint8arrays@2.1.10: - resolution: {integrity: sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==} - - uint8arrays@3.1.1: - resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - varint@5.0.2: - resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} - - varint@6.0.0: - resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} - - viem@2.38.5: - resolution: {integrity: sha512-EU2olUnWd5kBK1t3BicwaamPHGUANRYetoDLSVzDy7XQ8o8UswItnkQbufe3xTcdRCtb2JYMwjlgHZZ7fUoLdA==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.9.0: - resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xhr2-cookies@1.1.0: - resolution: {integrity: sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - znv@0.4.0: - resolution: {integrity: sha512-6/pGsQhBisLzKdyC90mUCRgYDtCfQ4aQ68sDybexq3GMzqqkp662GH6qIyuCHJC1i72hJPHbWAhccTJVuZUQfA==} - peerDependencies: - zod: ^3.13.2 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@adraffy/ens-normalize@1.11.1': {} - - '@assemblyscript/loader@0.9.4': {} - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-validator-identifier@7.28.5': {} - - '@endo/cache-map@1.1.0': {} - - '@endo/env-options@1.1.11': {} - - '@endo/immutable-arraybuffer@1.1.2': {} - - '@ethersproject/abi@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/abstract-provider@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - - '@ethersproject/abstract-signer@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/address@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/rlp': 5.8.0 - - '@ethersproject/base64@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - - '@ethersproject/bignumber@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - bn.js: 5.2.2 - - '@ethersproject/bytes@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/constants@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - - '@ethersproject/hash@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/keccak256@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - js-sha3: 0.8.0 - - '@ethersproject/logger@5.8.0': {} - - '@ethersproject/networks@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/properties@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/rlp@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/signing-key@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - bn.js: 5.2.2 - elliptic: 6.6.1 - hash.js: 1.1.7 - - '@ethersproject/strings@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/transactions@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - - '@ethersproject/web@5.8.0': - dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@iarna/toml@3.0.0': {} - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@multiformats/base-x@4.0.1': {} - - '@noble/ciphers@1.3.0': {} - - '@noble/curves@1.9.1': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.8.0': {} - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.4': {} - - '@protobufjs/eventemitter@1.1.0': {} - - '@protobufjs/fetch@1.1.0': - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - - '@protobufjs/float@1.0.2': {} - - '@protobufjs/inquire@1.1.0': {} - - '@protobufjs/path@1.1.2': {} - - '@protobufjs/pool@1.1.0': {} - - '@protobufjs/utf8@1.1.0': {} - - '@scure/base@1.2.6': {} - - '@scure/bip32@1.7.0': - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/bip39@1.6.0': - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@types/long@4.0.2': {} - - '@types/minimist@1.2.5': {} - - '@types/node@24.9.2': - dependencies: - undici-types: 7.16.0 - - '@types/normalize-package-data@2.4.4': {} - - '@usecannon/builder@2.25.1': - dependencies: - '@usecannon/router': 4.1.3 - '@usecannon/web-solc': 0.5.1 - acorn: 8.15.0 - axios: 1.13.1(debug@4.4.3) - axios-retry: 4.5.0(axios@1.13.1(debug@4.4.3)) - buffer: 6.0.3 - chalk: 4.1.2 - debug: 4.4.3 - deep-freeze: 0.0.1 - form-data: 4.0.4 - fuse.js: 7.1.0 - lodash: 4.17.21 - pako: 2.1.0 - promise-events: 0.2.4 - rfdc: 1.4.1 - ses: 1.14.0 - typestub-ipfs-only-hash: 4.0.0 - viem: 2.38.5(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - - '@usecannon/cli@2.25.1': - dependencies: - '@iarna/toml': 3.0.0 - '@usecannon/builder': 2.25.1 - abitype: 1.1.1(zod@3.25.76) - chalk: 4.1.2 - commander: 12.1.0 - debug: 4.4.3 - eth-provider: 0.13.7 - fs-extra: 11.3.2 - glob: 11.0.3 - lodash: 4.17.21 - nanospinner: 1.2.2 - prompts: 2.4.2 - semver: 7.7.3 - table: 6.9.0 - tildify: 3.0.0 - untildify: 4.0.0 - viem: 2.38.5(zod@3.25.76) - znv: 0.4.0(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - - '@usecannon/router@4.1.3': - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - debug: 4.4.3 - mustache: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@usecannon/web-solc@0.5.1': {} - - abitype@1.1.0(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - abitype@1.1.1(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - acorn@8.15.0: {} - - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - arrify@1.0.1: {} - - astral-regex@2.0.0: {} - - asynckit@0.4.0: {} - - axios-retry@4.5.0(axios@1.13.1(debug@4.4.3)): - dependencies: - axios: 1.13.1(debug@4.4.3) - is-retry-allowed: 2.2.0 - - axios@1.13.1(debug@4.4.3): - dependencies: - follow-redirects: 1.15.11(debug@4.4.3) - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - base64-js@1.5.1: {} - - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.2 - - blakejs@1.2.1: {} - - bn.js@4.12.2: {} - - bn.js@5.2.2: {} - - brorand@1.1.0: {} - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - - camelcase@5.3.1: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - cids@1.1.9: - dependencies: - multibase: 4.0.6 - multicodec: 3.2.1 - multihashes: 4.0.3 - uint8arrays: 3.1.1 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colorette@2.0.20: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@12.1.0: {} - - cookiejar@2.1.4: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - - deep-freeze@0.0.1: {} - - delayed-stream@1.0.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - elliptic@6.6.1: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - err-code@3.0.1: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - eth-provider@0.13.7: - dependencies: - ethereum-provider: 0.7.7 - events: 3.3.0 - oboe: 2.1.5 - uuid: 9.0.0 - ws: 8.9.0 - xhr2-cookies: 1.1.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - ethereum-provider@0.7.7: - dependencies: - events: 3.3.0 - - eventemitter3@5.0.1: {} - - events@3.3.0: {} - - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.0: {} - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - follow-redirects@1.15.11(debug@4.4.3): - optionalDependencies: - debug: 4.4.3 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - fs-extra@11.3.2: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - function-bind@1.1.2: {} - - fuse.js@7.1.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - glob@11.0.3: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - hamt-sharding@2.0.1: - dependencies: - sparse-array: 1.3.2 - uint8arrays: 3.1.1 - - hard-rejection@2.1.0: {} - - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - hosted-git-info@2.8.9: {} - - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - - http-https@1.0.0: {} - - ieee754@1.2.1: {} - - indent-string@4.0.0: {} - - inherits@2.0.4: {} - - interface-ipld-format@1.0.1: - dependencies: - cids: 1.1.9 - multicodec: 3.2.1 - multihashes: 4.0.3 - - ipfs-only-hash@4.0.0: - dependencies: - ipfs-unixfs-importer: 7.0.3 - meow: 9.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - ipfs-unixfs-importer@7.0.3: - dependencies: - bl: 5.1.0 - cids: 1.1.9 - err-code: 3.0.1 - hamt-sharding: 2.0.1 - ipfs-unixfs: 4.0.3 - ipld-dag-pb: 0.22.3 - it-all: 1.0.6 - it-batch: 1.0.9 - it-first: 1.0.7 - it-parallel-batch: 1.0.11 - merge-options: 3.0.4 - multihashing-async: 2.1.4 - rabin-wasm: 0.1.5 - uint8arrays: 2.1.10 - transitivePeerDependencies: - - encoding - - supports-color - - ipfs-unixfs@4.0.3: - dependencies: - err-code: 3.0.1 - protobufjs: 6.11.4 - - ipld-dag-pb@0.22.3: - dependencies: - cids: 1.1.9 - interface-ipld-format: 1.0.1 - multicodec: 3.2.1 - multihashing-async: 2.1.4 - protobufjs: 6.11.4 - stable: 0.1.8 - uint8arrays: 2.1.10 - - is-arrayish@0.2.1: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-fullwidth-code-point@3.0.0: {} - - is-plain-obj@1.1.0: {} - - is-plain-obj@2.1.0: {} - - is-retry-allowed@2.2.0: {} - - isexe@2.0.0: {} - - isows@1.0.7(ws@8.18.3): - dependencies: - ws: 8.18.3 - - it-all@1.0.6: {} - - it-batch@1.0.9: {} - - it-first@1.0.7: {} - - it-parallel-batch@1.0.11: - dependencies: - it-batch: 1.0.9 - - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - - js-sha3@0.8.0: {} - - js-tokens@4.0.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-traverse@1.0.0: {} - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - kind-of@6.0.3: {} - - kleur@3.0.3: {} - - lines-and-columns@1.2.4: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - lodash.truncate@4.4.2: {} - - lodash@4.17.21: {} - - long@4.0.0: {} - - lru-cache@11.2.2: {} - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - - math-intrinsics@1.1.0: {} - - meow@9.0.0: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - - merge-options@3.0.4: - dependencies: - is-plain-obj: 2.1.0 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - min-indent@1.0.1: {} - - minimalistic-assert@1.0.1: {} - - minimalistic-crypto-utils@1.0.1: {} - - minimatch@10.1.1: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - - minimist@1.2.8: {} - - minipass@7.1.2: {} - - ms@2.1.3: {} - - multibase@4.0.6: - dependencies: - '@multiformats/base-x': 4.0.1 - - multicodec@3.2.1: - dependencies: - uint8arrays: 3.1.1 - varint: 6.0.0 - - multiformats@9.9.0: {} - - multihashes@4.0.3: - dependencies: - multibase: 4.0.6 - uint8arrays: 3.1.1 - varint: 5.0.2 - - multihashing-async@2.1.4: - dependencies: - blakejs: 1.2.1 - err-code: 3.0.1 - js-sha3: 0.8.0 - multihashes: 4.0.3 - murmurhash3js-revisited: 3.0.0 - uint8arrays: 3.1.1 - - murmurhash3js-revisited@3.0.0: {} - - mustache@4.2.0: {} - - nanospinner@1.2.2: - dependencies: - picocolors: 1.1.1 - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.11 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 - - oboe@2.1.5: - dependencies: - http-https: 1.0.0 - - ox@0.9.6(zod@3.25.76): - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.1(zod@3.25.76) - eventemitter3: 5.0.1 - transitivePeerDependencies: - - zod - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - package-json-from-dist@1.0.1: {} - - pako@2.1.0: {} - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@2.0.0: - dependencies: - lru-cache: 11.2.2 - minipass: 7.1.2 - - picocolors@1.1.1: {} - - promise-events@0.2.4: {} - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - protobufjs@6.11.4: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/long': 4.0.2 - '@types/node': 24.9.2 - long: 4.0.0 - - proxy-from-env@1.1.0: {} - - quick-lru@4.0.1: {} - - rabin-wasm@0.1.5: - dependencies: - '@assemblyscript/loader': 0.9.4 - bl: 5.1.0 - debug: 4.4.3 - minimist: 1.2.8 - node-fetch: 2.7.0 - readable-stream: 3.6.2 - transitivePeerDependencies: - - encoding - - supports-color - - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - - require-from-string@2.0.2: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rfdc@1.4.1: {} - - safe-buffer@5.2.1: {} - - semver@5.7.2: {} - - semver@7.7.3: {} - - ses@1.14.0: - dependencies: - '@endo/cache-map': 1.1.0 - '@endo/env-options': 1.1.11 - '@endo/immutable-arraybuffer': 1.1.2 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - - sisteransi@1.0.5: {} - - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - - sparse-array@1.3.2: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 - - spdx-license-ids@3.0.22: {} - - stable@0.1.8: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - table@6.9.0: - dependencies: - ajv: 8.17.1 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - tildify@3.0.0: {} - - tr46@0.0.3: {} - - trim-newlines@3.0.1: {} - - type-fest@0.18.1: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - typestub-ipfs-only-hash@4.0.0: - dependencies: - ipfs-only-hash: 4.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - uint8arrays@2.1.10: - dependencies: - multiformats: 9.9.0 - - uint8arrays@3.1.1: - dependencies: - multiformats: 9.9.0 - - undici-types@7.16.0: {} - - universalify@2.0.1: {} - - untildify@4.0.0: {} - - util-deprecate@1.0.2: {} - - uuid@9.0.0: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - varint@5.0.2: {} - - varint@6.0.0: {} - - viem@2.38.5(zod@3.25.76): - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(zod@3.25.76) - isows: 1.0.7(ws@8.18.3) - ox: 0.9.6(zod@3.25.76) - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - - ws@8.18.3: {} - - ws@8.9.0: {} - - xhr2-cookies@1.1.0: - dependencies: - cookiejar: 2.1.4 - - yallist@4.0.0: {} - - yargs-parser@20.2.9: {} - - znv@0.4.0(zod@3.25.76): - dependencies: - colorette: 2.0.20 - zod: 3.25.76 - - zod@3.25.76: {} diff --git a/test/Dockerfile b/test/Dockerfile index 82a25d9d3..f742b521f 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -79,27 +79,6 @@ RUN make -C src slirp=no release=yes ENV DESTDIR=/dave/machine/emulator/rootfs RUN mkdir -p ${DESTDIR} && make install -#### pnpm stage -FROM base AS pnpm -ARG TARGETARCH -ARG TARGETOS -ARG PNPM_VERSION - -RUN < Date: Wed, 1 Jul 2026 08:19:07 -0300 Subject: [PATCH 023/113] chore: bump Foundry from 1.4.3 to 1.5.1 - This commit bumps Foundry on the CI and Dockefile. This is to align with the version of Foundry used on rollups-contracts, so that the node team can also bump Foundry (given that it uses the devnet image built in the dave repo still). --- .github/actions/setup-tools/action.yml | 2 +- prt/contracts/src/tournament/libs/Match.sol | 5 +---- test/Dockerfile | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml index e6e41e16b..7d1492852 100644 --- a/.github/actions/setup-tools/action.yml +++ b/.github/actions/setup-tools/action.yml @@ -12,7 +12,7 @@ inputs: foundry-version: description: 'Foundry version to install' required: false - default: 'v1.4.3' + default: 'v1.5.1' setup-qemu: description: 'Whether to setup QEMU for riscv support' required: false diff --git a/prt/contracts/src/tournament/libs/Match.sol b/prt/contracts/src/tournament/libs/Match.sol index 9f44149fb..6c6b403cb 100644 --- a/prt/contracts/src/tournament/libs/Match.sol +++ b/prt/contracts/src/tournament/libs/Match.sol @@ -195,10 +195,7 @@ library Match { return args.toCycle(state.runningLeafPosition); } - function getDivergence( - State memory state, - Commitment.Arguments memory args - ) + function getDivergence(State memory state, Commitment.Arguments memory args) internal pure returns ( diff --git a/test/Dockerfile b/test/Dockerfile index f742b521f..7ff9b09f9 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -2,7 +2,7 @@ ARG RUST_VERSION=1.90 ARG DEBIAN_VERSION=trixie -ARG FOUNDRY_VERSION=1.4.3 +ARG FOUNDRY_VERSION=1.5.1 ARG PNPM_VERSION=10.7.0 ARG JUST_VERSION=1.46.0 @@ -39,8 +39,8 @@ RUN < Date: Wed, 1 Jul 2026 10:47:43 -0300 Subject: [PATCH 024/113] chore: bump Honeypot from 3.0.0 to 3.0.1 - Honeypot 3.0.1 fixes a bug in the rootfs Dockerfile that would lead to its build to fail. The resulting image is the same as 3.0.0 but we can now build the custom Honeypot rootfs image with devnet config. --- test/programs/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/programs/justfile b/test/programs/justfile index 6f30ba3aa..5a604d045 100644 --- a/test/programs/justfile +++ b/test/programs/justfile @@ -33,7 +33,7 @@ clean-echo: (clean-program "echo") # honeypot/honeypot (requires docker) build-honeypot-snapshot: clean-honeypot-snapshot clean-honeypot-project git clone https://github.com/cartesi/honeypot.git honeypot/project - git -C honeypot/project reset --hard 34d00721a527eeb7ed8cce2a13a142e3d8de9aad # v3.0.0 + git -C honeypot/project reset --hard 589a9c1a26522904f79df4955caff69537d38b13 # v3.0.1 sed -i 's/,filename:/,data_filename:/g' honeypot/project/Makefile sed -i 's/--append-bootargs=ro/--append-bootargs=rw/g' honeypot/project/Makefile sed -i 's/label:state,length:4096,user:dapp/label:state,length:4096,user:dapp,mke2fs:false,mount:false/g' honeypot/project/Makefile From 48fcc2d4c95f82be0bc83defb2bd4476d89a22ff Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 1 Jul 2026 08:54:13 -0300 Subject: [PATCH 025/113] feat!: tournament result staging --- .../contracts/src/DaveAppFactory.sol | 47 +- .../contracts/src/DaveConsensus.sol | 137 ++++-- .../contracts/src/IDaveAppFactory.sol | 23 +- .../contracts/src/IDaveConsensus.sol | 111 ++++- .../contracts/test/DaveAppFactory.t.sol | 454 +++++++++++++++--- .../node/blockchain-reader/src/test_utils.rs | 17 +- cartesi-rollups/node/epoch-manager/src/lib.rs | 81 +++- prt/tests/rollups/dave/reader.lua | 5 +- prt/tests/rollups/dave/sender.lua | 5 +- 9 files changed, 727 insertions(+), 153 deletions(-) diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index c051735d5..df53067c4 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -34,26 +34,28 @@ contract DaveAppFactory is IDaveAppFactory { TOURNAMENT_FACTORY = tournamentFactory; } - function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) - external - override - returns (IApplication appContract, IDaveConsensus daveConsensus) - { + function newDaveApp( + bytes32 templateHash, + uint256 claimStagingPeriod, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external override returns (IApplication appContract, IDaveConsensus daveConsensus) { appContract = _newApplication(templateHash, withdrawalConfig, salt); - daveConsensus = _newDaveConsensus(address(appContract), templateHash, salt); + daveConsensus = _newDaveConsensus(address(appContract), templateHash, claimStagingPeriod, salt); appContract.migrateToOutputsMerkleRootValidator(daveConsensus); appContract.renounceOwnership(); emit DaveAppCreated(appContract, daveConsensus); } - function calculateDaveAppAddress(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) - external - view - override - returns (address appContractAddress, address daveConsensusAddress) - { + function calculateDaveAppAddress( + bytes32 templateHash, + uint256 claimStagingPeriod, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external view override returns (address appContractAddress, address daveConsensusAddress) { appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt); - daveConsensusAddress = _calculateDaveConsensusAddress(appContractAddress, templateHash, salt); + daveConsensusAddress = + _calculateDaveConsensusAddress(appContractAddress, templateHash, claimStagingPeriod, salt); } /// @notice Encode the data availability blob for applications that only use the input box as DA. @@ -76,12 +78,14 @@ contract DaveAppFactory is IDaveAppFactory { } /// @notice Instantiate a new `DaveConsensus` contract. - function _newDaveConsensus(address appContract, bytes32 templateHash, bytes32 salt) + function _newDaveConsensus(address appContract, bytes32 templateHash, uint256 claimStagingPeriod, bytes32 salt) internal returns (DaveConsensus) { Machine.Hash initialMachineStateHash = Machine.Hash.wrap(templateHash); - return new DaveConsensus{salt: salt}(INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash); + return new DaveConsensus{salt: salt}( + INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash, claimStagingPeriod + ); } /// @notice Calculates the address of an application contract. @@ -97,17 +101,18 @@ contract DaveAppFactory is IDaveAppFactory { } /// @notice Calculates the address of a `DaveConsensus` contract. - function _calculateDaveConsensusAddress(address appContract, bytes32 templateHash, bytes32 salt) - internal - view - returns (address) - { + function _calculateDaveConsensusAddress( + address appContract, + bytes32 templateHash, + uint256 claimStagingPeriod, + bytes32 salt + ) internal view returns (address) { return Create2.computeAddress( salt, keccak256( abi.encodePacked( type(DaveConsensus).creationCode, - abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash) + abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash, claimStagingPeriod) ) ) ); diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index bde7f1ad6..54cab71fb 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -27,28 +27,6 @@ import {Memory} from "step/src/Memory.sol"; import {IDaveConsensus} from "./IDaveConsensus.sol"; -/// @notice Consensus contract with Dave tournaments. -/// -/// @notice This contract validates only one application, -/// which read inputs from the InputBox contract. -/// -/// @notice This contract also manages epoch boundaries, which -/// are defined in terms of block numbers. We represent them -/// as intervals of the form [a,b). They are also identified by -/// incremental numbers that start from 0. -/// -/// @notice Off-chain nodes can listen to `EpochSealed` events -/// to know where epochs start and end, and which epochs have been -/// settled already and which one is open for challenges still. -/// Anyone can settle an epoch by calling `settle`. -/// One can also check if it can be settled by calling `canSettle`. -/// -/// @notice At any given time, there is always one sealed epoch. -/// Prior to it, every epoch has been settled. -/// After it, the next epoch is accumulating inputs. Once this epoch is settled, -/// the accumlating epoch will be sealed, and a new -/// accumulating epoch will be created. -/// contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { using LibMath for uint256; using LibBinaryMerkleTree for bytes; @@ -63,6 +41,9 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { /// @notice The contract used to instantiate tournaments ITournamentFactory immutable _TOURNAMENT_FACTORY; + /// @notice The claim staging period + uint256 immutable _CLAIM_STAGING_PERIOD; + /// @notice Deployment block number uint256 immutable _DEPLOYMENT_BLOCK_NUMBER = block.number; @@ -78,6 +59,21 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { /// @notice Current sealed epoch tournament ITournament _tournament; + /// @notice Whether the result of the current sealed epoch tournament is staged + bool _isTournamentResultStaged; + + /// @notice The number of the block in which the tournament result was staged + /// @dev Only meaningful if _isTournamentResultStaged is true. + uint256 _stagingBlockNumber; + + /// @notice The staged post-epoch machine state hash + /// @dev Only meaningful if _isTournamentResultStaged is true. + Machine.Hash _stagedPostEpochMachineStateHash; + + /// @notice The staged post-epoch outputs Merkle root + /// @dev Only meaningful if _isTournamentResultStaged is true. + bytes32 _stagedPostEpochOutputsMerkleRoot; + /// @notice Settled output trees' merkle root hash mapping(bytes32 => bool) _outputsMerkleRoots; @@ -88,12 +84,14 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { IInputBox inputBox, address appContract, ITournamentFactory tournamentFactory, - Machine.Hash initialMachineStateHash + Machine.Hash initialMachineStateHash, + uint256 claimStagingPeriod ) { // Initialize immutable variables _INPUT_BOX = inputBox; _APP_CONTRACT = appContract; _TOURNAMENT_FACTORY = tournamentFactory; + _CLAIM_STAGING_PERIOD = claimStagingPeriod; emit ConsensusCreation(inputBox, appContract, tournamentFactory); // Initialize first sealed epoch @@ -104,17 +102,24 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, bytes32(0), tournament); } - function canSettle() + function canStageTournamentResult() external view override - returns (bool isFinished, uint256 epochNumber, Tree.Node winnerCommitment) + returns ( + bool isFinished, + bool isTournamentResultStaged, + uint256 epochNumber, + Tree.Node winnerCommitment, + Machine.Hash winnerPostEpochMachineStateHash + ) { - (isFinished, winnerCommitment,) = _tournament.arbitrationResult(); epochNumber = _epochNumber; + isTournamentResultStaged = _isTournamentResultStaged; + (isFinished, winnerCommitment, winnerPostEpochMachineStateHash) = _tournament.arbitrationResult(); } - function settle(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) + function stageTournamentResult(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) external override notForeclosed(_APP_CONTRACT) @@ -122,21 +127,77 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { // Check tournament settlement require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber)); + // Check whether the tournament result is staged + require(!_isTournamentResultStaged, TournamentResultAlreadyStaged()); + // Check tournament finished (bool isFinished,, Machine.Hash finalMachineStateHash) = _tournament.arbitrationResult(); require(isFinished, TournamentNotFinishedYet()); - ITournament oldTournament = _tournament; - _tournament = ITournament(address(0)); // Check outputs Merkle root _validateOutputTree(finalMachineStateHash, outputsMerkleRoot, proof); + // Stage tournament result, and store the current block number for + // later checking whether the claim staging period has elapsed + _stagingBlockNumber = block.number; + _stagedPostEpochMachineStateHash = finalMachineStateHash; + _stagedPostEpochOutputsMerkleRoot = outputsMerkleRoot; + _isTournamentResultStaged = true; + + // Try recovering bond for tournament winner + try _tournament.tryRecoveringBond() {} catch {} + + emit EpochStaged(epochNumber, finalMachineStateHash, outputsMerkleRoot); + } + + function canAcceptStagedTournamentResult() + external + view + override + returns ( + bool isTournamentResultStaged, + bool isClaimStagingPeriodOver, + uint256 epochNumber, + Machine.Hash stagedPostEpochMachineStateHash, + bytes32 stagedPostEpochOutputsMerkleRoot + ) + { + epochNumber = _epochNumber; + isTournamentResultStaged = _isTournamentResultStaged; + if (_isTournamentResultStaged) { + isClaimStagingPeriodOver = ((block.number - _stagingBlockNumber) >= _CLAIM_STAGING_PERIOD); + stagedPostEpochMachineStateHash = _stagedPostEpochMachineStateHash; + stagedPostEpochOutputsMerkleRoot = _stagedPostEpochOutputsMerkleRoot; + } + } + + function acceptStagedTournamentResult(uint256 epochNumber) external override notForeclosed(_APP_CONTRACT) { + // Check tournament settlement + require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber)); + + // Check whether the tournament result is staged + require(_isTournamentResultStaged, TournamentResultNotStaged()); + + // Check whether the claim staging period has elapsed + { + uint256 numberOfBlocksAfterStaging = block.number - _stagingBlockNumber; + require( + numberOfBlocksAfterStaging >= _CLAIM_STAGING_PERIOD, + ClaimStagingPeriodNotOverYet(numberOfBlocksAfterStaging, _CLAIM_STAGING_PERIOD) + ); + } + + // Get staged tournament result + Machine.Hash finalMachineStateHash = _stagedPostEpochMachineStateHash; + bytes32 outputsMerkleRoot = _stagedPostEpochOutputsMerkleRoot; + // Seal current accumulating epoch, save settled output tree and machine state hash _epochNumber++; _inputIndexLowerBound = _inputIndexUpperBound; _inputIndexUpperBound = _INPUT_BOX.getNumberOfInputs(_APP_CONTRACT); _outputsMerkleRoots[outputsMerkleRoot] = true; _lastFinalizedMachineStateHash = finalMachineStateHash; + _isTournamentResultStaged = false; // Start new tournament _tournament = _TOURNAMENT_FACTORY.instantiate(finalMachineStateHash, this); @@ -149,8 +210,6 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { outputsMerkleRoot, _tournament ); - - oldTournament.tryRecoveringBond(); } function getCurrentSealedEpoch() @@ -161,13 +220,23 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, - ITournament tournament + ITournament tournament, + bool isTournamentResultStaged, + uint256 stagingBlockNumber, + Machine.Hash stagedPostEpochMachineStateHash, + bytes32 stagedPostEpochOutputsMerkleRoot ) { epochNumber = _epochNumber; inputIndexLowerBound = _inputIndexLowerBound; inputIndexUpperBound = _inputIndexUpperBound; tournament = _tournament; + isTournamentResultStaged = _isTournamentResultStaged; + if (_isTournamentResultStaged) { + stagingBlockNumber = _stagingBlockNumber; + stagedPostEpochMachineStateHash = _stagedPostEpochMachineStateHash; + stagedPostEpochOutputsMerkleRoot = _stagedPostEpochOutputsMerkleRoot; + } } function getInputBox() external view override returns (IInputBox) { @@ -182,6 +251,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { return _TOURNAMENT_FACTORY; } + function getClaimStagingPeriod() external view override returns (uint256) { + return _CLAIM_STAGING_PERIOD; + } + function provideMerkleRootOfInput(uint256 inputIndexWithinEpoch, bytes calldata input) external view diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index 7715778da..12cc10587 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -11,7 +11,7 @@ import {IDaveConsensus} from "./IDaveConsensus.sol"; /// @title Dave-App Pair Factory /// @notice Allows anyone to reliably deploy an application -/// validated a newly-deployed `IDaveConsensus` contract. +/// validated by a newly-deployed `IDaveConsensus` contract. interface IDaveAppFactory is IApplicationFactoryErrors { /// @notice A Dave-App pair was created. /// @param appContract The application contract @@ -20,22 +20,29 @@ interface IDaveAppFactory is IApplicationFactoryErrors { /// @notice Deploy a new Dave-App pair deterministically. /// @param templateHash The application template hash + /// @param claimStagingPeriod The claim staging period /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContract The application contract /// @return daveConsensus The Dave consensus contract - function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) - external - returns (IApplication appContract, IDaveConsensus daveConsensus); + function newDaveApp( + bytes32 templateHash, + uint256 claimStagingPeriod, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external returns (IApplication appContract, IDaveConsensus daveConsensus); /// @notice Calculate the address of a Dave-App pair. /// @param templateHash The application template hash + /// @param claimStagingPeriod The claim staging period /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContractAddress The application contract address /// @return daveConsensusAddress The Dave consensus contract address - function calculateDaveAppAddress(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) - external - view - returns (address appContractAddress, address daveConsensusAddress); + function calculateDaveAppAddress( + bytes32 templateHash, + uint256 claimStagingPeriod, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external view returns (address appContractAddress, address daveConsensusAddress); } diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 092228b20..1b1b2af7f 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -23,21 +23,27 @@ import {Tree} from "prt-contracts/types/Tree.sol"; /// which read inputs from the InputBox contract. /// /// @notice This contract also manages epoch boundaries, which -/// are defined in terms of block numbers. We represent them +/// are defined in terms of input indices. We represent them /// as intervals of the form [a,b). They are also identified by /// incremental numbers that start from 0. /// /// @notice Off-chain nodes can listen to `EpochSealed` events /// to know where epochs start and end, and which epochs have been /// settled already and which one is open for challenges still. -/// Anyone can settle an epoch by calling `settle`. -/// One can also check if it can be settled by calling `canSettle`. +/// Anyone can stage a tournament result by calling `stageTournamentResult`. +/// One can also check if it can be staged by calling `canStageTournamentResult`. +/// Anyone can settle an epoch by calling `acceptStagedTournamentResult`. +/// One can also check if it can be settled by calling `canAcceptStagedTournamentResult`. /// /// @notice At any given time, there is always one sealed epoch. /// Prior to it, every epoch has been settled. /// After it, the next epoch is accumulating inputs. Once this epoch is settled, -/// the accumlating epoch will be sealed, and a new +/// the accumulating epoch will be sealed, and a new /// accumulating epoch will be created. +/// Every sealed epoch has an associated tournament. +/// Once a tournament is finished, and a winner commitment is declared, +/// it can then be staged by anyone. After the claim staging period is elapsed, +/// anyone can settle the epoch by accepting the staged winner commitment. /// interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplicationChecker, BinaryMerkleTreeErrors { /// @notice Consensus contract was created @@ -62,6 +68,14 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica ITournament tournament ); + /// @notice An epoch was staged + /// @param epochNumber the sealed epoch number + /// @param stagedPostEpochMachineStateHash The staged post-epoch machine state hash + /// @param stagedPostEpochOutputsMerkleRoot The staged post-epoch outputs Merkle root + event EpochStaged( + uint256 epochNumber, Machine.Hash stagedPostEpochMachineStateHash, bytes32 stagedPostEpochOutputsMerkleRoot + ); + /// @notice Received epoch number is different from actual /// @param received The epoch number received as argument /// @param actual The actual epoch number in storage @@ -70,6 +84,17 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica /// @notice Tournament is not finished yet error TournamentNotFinishedYet(); + /// @notice Tournament result is not yet staged + error TournamentResultNotStaged(); + + /// @notice Tournament result was already staged + error TournamentResultAlreadyStaged(); + + /// @notice The tournament result was staged but the claim staging period is not over yet. + /// @param numberOfBlocksAfterStaging The number of blocks since the claim was staged + /// @param claimStagingPeriod The claim staging period, in number of blocks + error ClaimStagingPeriodNotOverYet(uint256 numberOfBlocksAfterStaging, uint256 claimStagingPeriod); + /// @notice Hash of received input blob is different from stored on-chain /// @param fromReceivedInput Hash of received input blob /// @param fromInputBox Hash of input stored on the input box contract @@ -100,11 +125,23 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica /// @notice Get the tournament factory contract used to instantiate root tournaments. function getTournamentFactory() external view returns (ITournamentFactory); - /// @notice Get the current sealed epoch number, boundaries, and tournament. - /// @param epochNumber The epoch number - /// @param inputIndexLowerBound The epoch input index (inclusive) lower bound - /// @param inputIndexUpperBound The epoch input index (exclusive) upper bound - /// @param tournament The tournament that will decide the post-epoch state + /// @notice Get the number of base-layer blocks after which a staged claim can be accepted. + /// @dev A claim, in the context of PRT, is the winner commitment of a tournament, if there is one. + /// Once a tournament finishes, and a winner is declared, anyone can stage the tournament result, + /// and, after the claim staging period is elapsed, accept it into finality. + function getClaimStagingPeriod() external view returns (uint256); + + /// @notice Get the current sealed epoch number, boundaries, tournament, and staging info. + /// @return epochNumber The epoch number + /// @return inputIndexLowerBound The epoch input index (inclusive) lower bound + /// @return inputIndexUpperBound The epoch input index (exclusive) upper bound + /// @return tournament The tournament that will decide the post-epoch state + /// @return isTournamentResultStaged Whether the tournament result (if there is one) is staged + /// @return stagingBlockNumber The number of the block in which the tournament result was staged + /// @return stagedPostEpochMachineStateHash The staged post-epoch machine state hash + /// @return stagedPostEpochOutputsMerkleRoot The staged post-epoch outputs Merkle root + /// @dev The values of stagingBlockNumber, stagedPostEpochMachineStateHash, and stagedPostEpochOutputsMerkleRoot + /// only have meaning if the value of isTournamentResultStaged is true. function getCurrentSealedEpoch() external view @@ -112,19 +149,61 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, - ITournament tournament + ITournament tournament, + bool isTournamentResultStaged, + uint256 stagingBlockNumber, + Machine.Hash stagedPostEpochMachineStateHash, + bytes32 stagedPostEpochOutputsMerkleRoot ); - /// @notice Check whether the current sealed epoch can be settled. - /// @return isFinished Whether the current sealed epoch tournament has finished yet + /// @notice Check whether the tournament result of the current sealed epoch can be staged. + /// @return isFinished Whether the current sealed epoch tournament is finished + /// @return isTournamentResultStaged Whether the tournament result (if there is one) is staged /// @return epochNumber The current sealed epoch number - /// @return winnerCommitment If the tournament has finished, the winning commitment - function canSettle() external view returns (bool isFinished, uint256 epochNumber, Tree.Node winnerCommitment); + /// @return winnerCommitment If the tournament has finished, the winner commitment + /// @return winnerPostEpochMachineStateHash If the tournament has finished, the winner post-epoch machine state hash + /// @dev Validators should only call `stageTournamentResult` if isFinished is true and isTournamentResultStaged is false. + function canStageTournamentResult() + external + view + returns ( + bool isFinished, + bool isTournamentResultStaged, + uint256 epochNumber, + Tree.Node winnerCommitment, + Machine.Hash winnerPostEpochMachineStateHash + ); - /// @notice Settle the current sealed epoch. + /// @notice Stage the tournament result of the current sealed epoch. /// @param epochNumber The current sealed epoch number (used to avoid race conditions) /// @param outputsMerkleRoot The post-epoch outputs Merkle root (used to validate outputs) /// @param proof The bottom-up Merkle proof of the outputs Merkle root in the final machine state + /// @dev On success, emits an `EpochStaged` event. + function stageTournamentResult(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) external; + + /// @notice Check whether the staged tournament result of the current sealed epoch can be accepted. + /// @return isTournamentResultStaged Whether the tournament result (if there is one) is staged + /// @return isClaimStagingPeriodOver Whether the claim staging period is over + /// @return epochNumber The current sealed epoch number + /// @return stagedPostEpochMachineStateHash If the tournament result is staged, the staged post-epoch machine state hash + /// @return stagedPostEpochOutputsMerkleRoot If the tournament result is staged, the staged post-epoch outputs Merkle root + /// @dev Validators should only call `acceptStagedTournamentResult` if both isTournamentResultStaged + /// and isClaimStagingPeriodOver are true. Be also mindful that isClaimStagingPeriodOver, + /// stagedPostEpochMachineStateHash, and stagedPostEpochOutputsMerkleRoot only have any meaning + /// if isTournamentResultStaged is true. + function canAcceptStagedTournamentResult() + external + view + returns ( + bool isTournamentResultStaged, + bool isClaimStagingPeriodOver, + uint256 epochNumber, + Machine.Hash stagedPostEpochMachineStateHash, + bytes32 stagedPostEpochOutputsMerkleRoot + ); + + /// @notice Accept the staged tournament result of the current sealed epoch. + /// @param epochNumber The current sealed epoch number (used to avoid race conditions) /// @dev On success, emits an `EpochSealed` event. - function settle(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) external; + function acceptStagedTournamentResult(uint256 epochNumber) external; } diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index c67c4bb1d..4868d6356 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -86,15 +86,20 @@ contract DaveAppFactoryTest is Test { _daveAppFactory = new DaveAppFactory(_inputBox, _appFactory, _tournamentFactory); } - function testNewDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) external { - _randomizeBlockNumber(); + function testNewDaveApp( + bytes32 templateHash, + uint64 claimStagingPeriod, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external { + _randomizeBlockNumber(claimStagingPeriod); (address precalculatedAppContractAddress, address precalculatedDaveConsensusAddress) = - _daveAppFactory.calculateDaveAppAddress(templateHash, withdrawalConfig, salt); + _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, withdrawalConfig, salt); vm.recordLogs(); - try _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt) returns ( + try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt) returns ( IApplication appContract, IDaveConsensus daveConsensus ) { Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -111,10 +116,10 @@ contract DaveAppFactoryTest is Test { "calculateDaveAppAddress(...)[1] != newDaveApp(...)[1]" ); - _testNewDaveAppSuccess(templateHash, withdrawalConfig, appContract, daveConsensus, logs); + _testNewDaveAppSuccess(templateHash, claimStagingPeriod, withdrawalConfig, appContract, daveConsensus, logs); (precalculatedAppContractAddress, precalculatedDaveConsensusAddress) = - _daveAppFactory.calculateDaveAppAddress(templateHash, withdrawalConfig, salt); + _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, withdrawalConfig, salt); assertEq( precalculatedAppContractAddress, @@ -133,7 +138,7 @@ contract DaveAppFactoryTest is Test { } // Cannot deploy an application with the same salt twice - try _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt) { + try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt) { revert("second deterministic deployment did not revert"); } catch (bytes memory errorData) { assertEq( @@ -142,21 +147,22 @@ contract DaveAppFactoryTest is Test { } } - function testSettle( + function testStageAndAcceptTournamentResult( bytes32 templateHash, + uint64 claimStagingPeriod, WithdrawalConfig calldata withdrawalConfig, bytes32 salt, bytes32 outputsMerkleRoot, - bytes[] calldata inputPayloads, - bool foreclose + bytes[] calldata inputPayloads ) external { - _randomizeBlockNumber(); + _randomizeBlockNumber(claimStagingPeriod); IApplication appContract; IDaveConsensus daveConsensus; vm.assumeNoRevert(); - (appContract, daveConsensus) = _daveAppFactory.newDaveApp(templateHash, withdrawalConfig, salt); + (appContract, daveConsensus) = + _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt); bytes[] memory inputs = new bytes[](inputPayloads.length); @@ -164,7 +170,7 @@ contract DaveAppFactoryTest is Test { inputs[i] = _addInput(address(appContract), inputPayloads[i]); } - (,,, ITournament tournament) = daveConsensus.getCurrentSealedEpoch(); + (,,, ITournament tournament,,,,) = daveConsensus.getCurrentSealedEpoch(); bytes32[] memory outputsMerkleRootProof = _randomProof(Memory.LOG2_MAX_SIZE); bytes32 machineMerkleRoot = outputsMerkleRootProof.merkleRootAfterReplacement( @@ -250,34 +256,47 @@ contract DaveAppFactoryTest is Test { uint256 val2; uint256 val3; ITournament val4; + bool val5; - (val1, val2, val3, val4) = daveConsensus.getCurrentSealedEpoch(); + (val1, val2, val3, val4, val5,,,) = daveConsensus.getCurrentSealedEpoch(); assertEq(val1, 0); // epochNumber assertEq(val2, 0); // inputIndexLowerBound assertEq(val3, 0); // inputIndexUpperBound assertEq(address(val4), address(tournament)); + assertFalse(val5); // isTournamentResultStaged } - // Check epoch settlement readiness + // Check epoch staging readiness { bool val1; - uint256 val2; + bool val2; + uint256 val3; - (val1, val2,) = daveConsensus.canSettle(); + (val1, val2, val3,,) = daveConsensus.canStageTournamentResult(); assertFalse(val1); // isFinished - assertEq(val2, 0); // epochNumber + assertFalse(val2); // isTournamentResultStaged + assertEq(val3, 0); // epochNumber } - address settler = vm.randomAddress(); + // Check epoch acceptance readiness + { + bool val1; + uint256 val2; + + (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + + assertFalse(val1); // isTournamentResultStaged + assertEq(val2, 0); // epochNumber + } - vm.startPrank(settler); vm.expectRevert(IDaveConsensus.TournamentNotFinishedYet.selector); - daveConsensus.settle(0, outputsMerkleRoot, outputsMerkleRootProof); - vm.stopPrank(); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(0, outputsMerkleRoot, outputsMerkleRootProof); - vm.roll(vm.randomUint(vm.getBlockNumber() + Time.Duration.unwrap(MAX_ALLOWANCE), type(uint64).max)); + uint64 maxBlockNumber = type(uint64).max - claimStagingPeriod; + vm.roll(vm.randomUint(vm.getBlockNumber() + Time.Duration.unwrap(MAX_ALLOWANCE), maxBlockNumber)); assertTrue(tournament.isClosed()); assertTrue(tournament.isFinished()); @@ -300,72 +319,306 @@ contract DaveAppFactoryTest is Test { uint256 val2; uint256 val3; ITournament val4; + bool val5; - (val1, val2, val3, val4) = daveConsensus.getCurrentSealedEpoch(); + (val1, val2, val3, val4, val5,,,) = daveConsensus.getCurrentSealedEpoch(); assertEq(val1, 0); // epochNumber assertEq(val2, 0); // inputIndexLowerBound assertEq(val3, 0); // inputIndexUpperBound assertEq(address(val4), address(tournament)); + assertFalse(val5); // isTournamentResultStaged } - // Check epoch settlement readiness + // Check epoch staging readiness { bool val1; - uint256 val2; - Tree.Node val3; + bool val2; + uint256 val3; + Tree.Node val4; + Machine.Hash val5; - (val1, val2, val3) = daveConsensus.canSettle(); + (val1, val2, val3, val4, val5) = daveConsensus.canStageTournamentResult(); assertTrue(val1); // isFinished + assertFalse(val2); // isTournamentResultStaged + assertEq(val3, 0); // epochNumber + assertEq(Tree.Node.unwrap(val4), commitment); + assertEq(Machine.Hash.unwrap(val5), machineMerkleRoot); + } + + // Check epoch acceptance readiness + { + bool val1; + uint256 val2; + + (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + + assertFalse(val1); // isTournamentResultStaged assertEq(val2, 0); // epochNumber - assertEq(Tree.Node.unwrap(val3), commitment); } - vm.startPrank(settler); + // Try staging tournament result with an invalid epoch number { uint256 incorrectEpochNumber = vm.randomUint(1, type(uint256).max); vm.expectRevert(_encodeIncorrectEpochNumber(incorrectEpochNumber, 0)); - daveConsensus.settle(incorrectEpochNumber, outputsMerkleRoot, outputsMerkleRootProof); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(incorrectEpochNumber, outputsMerkleRoot, outputsMerkleRootProof); + } + + // Try staging tournament result with invalid outputs Merkle root proof size + while (true) { + uint256 invalidProofSize = vm.randomUint(0, 2 * outputsMerkleRootProof.length + 1); + if (invalidProofSize != outputsMerkleRootProof.length) { + bytes32[] memory invalidOutputsMerkleRootProof = _randomProof(invalidProofSize); + vm.expectRevert(_encodeInvalidOutputsMerkleRootProofSize(invalidProofSize)); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(0, outputsMerkleRoot, invalidOutputsMerkleRootProof); + break; + } } - vm.stopPrank(); - if (foreclose) { - vm.startPrank(appContract.getGuardian()); - appContract.foreclose(); - vm.stopPrank(); + // Try staging tournament result with invalid outputs Merkle root + while (true) { + bytes32 invalidOutputsMerkleRoot = bytes32(vm.randomUint()); + if (invalidOutputsMerkleRoot != outputsMerkleRoot) { + vm.expectRevert(_encodeInvalidOutputsMerkleRootProof(machineMerkleRoot)); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(0, invalidOutputsMerkleRoot, outputsMerkleRootProof); + break; + } } + vm.expectRevert(_encodeApplicationForeclosed(address(appContract))); + this.simulateForeclosureAndStaging(appContract, daveConsensus, 0, outputsMerkleRoot, outputsMerkleRootProof); + + uint256 stagingBlockNumber = vm.getBlockNumber(); + vm.recordLogs(); - vm.startPrank(settler); - try daveConsensus.settle(0, outputsMerkleRoot, outputsMerkleRootProof) { - assertFalse(foreclose); - } catch (bytes memory errorData) { - (bool isValidError, bytes32 errorSelector,) = errorData.consumeBytes4(); - assertTrue(isValidError, "Expected error to contain a 4-byte selector"); - if (errorSelector == IApplicationChecker.ApplicationForeclosed.selector) { - assertTrue(foreclose, "Application was foreclosed prior to settlement attempt"); - assertTrue(appContract.isForeclosed(), "Application is indeed foreclosed"); - return; // do not continue test case + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(0, outputsMerkleRoot, outputsMerkleRootProof); + + logs = vm.getRecordedLogs(); + + uint256 numOfEpochStagedEvents; + + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(daveConsensus)) { + if (log.topics[0] == IDaveConsensus.EpochStaged.selector) { + ++numOfEpochStagedEvents; + + uint256 arg1; + bytes32 arg2; + bytes32 arg3; + + (arg1, arg2, arg3) = abi.decode(log.data, (uint256, bytes32, bytes32)); + + assertEq(arg1, 0); // epochNumber + assertEq(arg2, machineMerkleRoot); // stagedPostEpochMachineStateHash + assertEq(arg3, outputsMerkleRoot); // stagedPostEpochOutputsMerkleRoot + } else { + revert UnexpectedLogTopic0(log); + } } else { - revert("Unexpected error"); + revert UnexpectedLogEmitter(log); } } - vm.stopPrank(); + + assertEq(numOfEpochStagedEvents, 1); + + // Check current sealed epoch + { + uint256 val1; + uint256 val2; + uint256 val3; + ITournament val4; + bool val5; + uint256 val6; + Machine.Hash val7; + bytes32 val8; + + (val1, val2, val3, val4, val5, val6, val7, val8) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 0); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, 0); // inputIndexUpperBound + assertEq(address(val4), address(tournament)); + assertTrue(val5); // isTournamentResultStaged + assertEq(val6, stagingBlockNumber); + assertEq(Machine.Hash.unwrap(val7), machineMerkleRoot); + assertEq(val8, outputsMerkleRoot); + } + + // Check epoch staging readiness + { + bool val1; + bool val2; + uint256 val3; + Tree.Node val4; + Machine.Hash val5; + + (val1, val2, val3, val4, val5) = daveConsensus.canStageTournamentResult(); + + assertTrue(val1); // isFinished + assertTrue(val2); // isTournamentResultStaged + assertEq(val3, 0); // epochNumber + assertEq(Tree.Node.unwrap(val4), commitment); + assertEq(Machine.Hash.unwrap(val5), machineMerkleRoot); + } + + // Check epoch acceptance readiness + { + bool val1; + bool val2; + uint256 val3; + Machine.Hash val4; + bytes32 val5; + + (val1, val2, val3, val4, val5) = daveConsensus.canAcceptStagedTournamentResult(); + + assertTrue(val1); // isTournamentResultStaged + assertEq(val2, claimStagingPeriod == 0); // isClaimStagingPeriodOver + assertEq(val3, 0); // epochNumber + assertEq(Machine.Hash.unwrap(val4), machineMerkleRoot); + assertEq(val5, outputsMerkleRoot); + } + + assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); + assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + + // Try re-staging tournament result + vm.expectRevert(IDaveConsensus.TournamentResultAlreadyStaged.selector); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(0, outputsMerkleRoot, outputsMerkleRootProof); + + // Try accepting tournament result before claim staging period is over + if (claimStagingPeriod >= 1) { + uint256 numberOfBlocksAfterStaging = vm.randomUint(0, claimStagingPeriod - 1); + vm.roll(stagingBlockNumber + numberOfBlocksAfterStaging); + vm.expectRevert(_encodeClaimStagingPeriodNotOverYet(numberOfBlocksAfterStaging, claimStagingPeriod)); + vm.prank(vm.randomAddress()); + daveConsensus.acceptStagedTournamentResult(0); + } + + vm.roll(vm.randomUint(stagingBlockNumber + claimStagingPeriod, type(uint64).max)); + + // Check current sealed epoch + { + uint256 val1; + uint256 val2; + uint256 val3; + ITournament val4; + bool val5; + uint256 val6; + Machine.Hash val7; + bytes32 val8; + + (val1, val2, val3, val4, val5, val6, val7, val8) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 0); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, 0); // inputIndexUpperBound + assertEq(address(val4), address(tournament)); + assertTrue(val5); // isTournamentResultStaged + assertEq(val6, stagingBlockNumber); + assertEq(Machine.Hash.unwrap(val7), machineMerkleRoot); + assertEq(val8, outputsMerkleRoot); + } + + // Check epoch staging readiness + { + bool val1; + bool val2; + uint256 val3; + Tree.Node val4; + Machine.Hash val5; + + (val1, val2, val3, val4, val5) = daveConsensus.canStageTournamentResult(); + + assertTrue(val1); // isFinished + assertTrue(val2); // isTournamentResultStaged + assertEq(val3, 0); // epochNumber + assertEq(Tree.Node.unwrap(val4), commitment); + assertEq(Machine.Hash.unwrap(val5), machineMerkleRoot); + } + + // Check epoch acceptance readiness + { + bool val1; + bool val2; + uint256 val3; + Machine.Hash val4; + bytes32 val5; + + (val1, val2, val3, val4, val5) = daveConsensus.canAcceptStagedTournamentResult(); + + assertTrue(val1); // isTournamentResultStaged + assertTrue(val2); // isClaimStagingPeriodOver + assertEq(val3, 0); // epochNumber + assertEq(Machine.Hash.unwrap(val4), machineMerkleRoot); + assertEq(val5, outputsMerkleRoot); + } + + assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); + assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + + vm.expectRevert(_encodeApplicationForeclosed(address(appContract))); + this.simulateForeclosureAndAcceptance(appContract, daveConsensus, 0); + + vm.recordLogs(); + + vm.prank(vm.randomAddress()); + daveConsensus.acceptStagedTournamentResult(0); logs = vm.getRecordedLogs(); + // Check current sealed epoch { uint256 val1; uint256 val2; uint256 val3; + ITournament val4; + bool val5; - (val1, val2, val3, tournament) = daveConsensus.getCurrentSealedEpoch(); + (val1, val2, val3, val4, val5,,,) = daveConsensus.getCurrentSealedEpoch(); assertEq(val1, 1); // epochNumber assertEq(val2, 0); // inputIndexLowerBound assertEq(val3, inputs.length); // inputIndexUpperBound + tournament = val4; + assertFalse(val5); // isTournamentResultStaged + } + + // Arbitration result + { + (bool isFinished,,) = tournament.arbitrationResult(); + assertFalse(isFinished); + } + + // Check epoch staging readiness + { + bool val1; + bool val2; + uint256 val3; + + (val1, val2, val3,,) = daveConsensus.canStageTournamentResult(); + + assertFalse(val1); // isFinished + assertFalse(val2); // isTournamentResultStaged + assertEq(val3, 1); // epochNumber + } + + // Check epoch acceptance readiness + { + bool val1; + uint256 val2; + + (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + + assertFalse(val1); // isTournamentResultStaged + assertEq(val2, 1); // epochNumber } uint256 numOfTournamentCreatedEvents; @@ -402,6 +655,8 @@ contract DaveAppFactoryTest is Test { assertEq(arg4, machineMerkleRoot); // initialMachineStateHash assertEq(arg5, outputsMerkleRoot); assertEq(arg6, address(tournament)); + } else { + revert UnexpectedLogTopic0(log); } } else { revert UnexpectedLogEmitter(log); @@ -420,15 +675,48 @@ contract DaveAppFactoryTest is Test { } { - uint256 inputIndexWithinBounds = vm.randomUint(inputs.length, type(uint256).max); + uint256 inputIndexOutOfBounds = vm.randomUint(inputs.length, type(uint256).max); uint256 inputLength = vm.randomUint(0, 100); bytes memory input = vm.randomBytes(inputLength); - assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexWithinBounds, input), bytes32(0)); + assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexOutOfBounds, input), bytes32(0)); } } + /// @notice This function is used to simulate a foreclosure and a tournament-result staging. + /// If the staging succeeds, then the function reverts with error message "Successful staging". + /// If the staging fails, then the function propagates the error from the DaveConsensus contract. + function simulateForeclosureAndStaging( + IApplication appContract, + IDaveConsensus daveConsensus, + uint256 epochNumber, + bytes32 outputsMerkleRoot, + bytes32[] calldata proof + ) external { + vm.prank(appContract.getGuardian()); + appContract.foreclose(); + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(epochNumber, outputsMerkleRoot, proof); + revert("Successful staging"); + } + + /// @notice This function is used to simulate a foreclosure and a tournament-result acceptance. + /// If the acceptance succeeds, then the function reverts with error message "Successful acceptance". + /// If the acceptance fails, then the function propagates the error from the DaveConsensus contract. + function simulateForeclosureAndAcceptance( + IApplication appContract, + IDaveConsensus daveConsensus, + uint256 epochNumber + ) external { + vm.prank(appContract.getGuardian()); + appContract.foreclose(); + vm.prank(vm.randomAddress()); + daveConsensus.acceptStagedTournamentResult(epochNumber); + revert("Successful acceptance"); + } + function _testNewDaveAppSuccess( bytes32 templateHash, + uint64 claimStagingPeriod, WithdrawalConfig calldata withdrawalConfig, IApplication appContract, IDaveConsensus daveConsensus, @@ -449,12 +737,19 @@ contract DaveAppFactoryTest is Test { uint256 val1; uint256 val2; uint256 val3; + ITournament val4; + bool val5; + uint256 val6; + Machine.Hash val7; + bytes32 val8; - (val1, val2, val3, tournament) = daveConsensus.getCurrentSealedEpoch(); + (val1, val2, val3, val4, val5, val6, val7, val8) = daveConsensus.getCurrentSealedEpoch(); assertEq(val1, 0); // epochNumber assertEq(val2, 0); // inputIndexLowerBound assertEq(val3, 0); // inputIndexUpperBound + tournament = val4; + assertFalse(val5); // isTournamentResultStaged } for (uint256 i; i < logs.length; ++i) { @@ -612,17 +907,20 @@ contract DaveAppFactoryTest is Test { // Check epoch settlement readiness { bool val1; - uint256 val2; + bool val2; + uint256 val3; - (val1, val2,) = daveConsensus.canSettle(); + (val1, val2, val3,,) = daveConsensus.canStageTournamentResult(); assertFalse(val1); // isFinished - assertEq(val2, 0); // epochNumber + assertFalse(val2); // isTournamentResultStaged + assertEq(val3, 0); // epochNumber } assertEq(address(daveConsensus.getInputBox()), address(_inputBox)); assertEq(address(daveConsensus.getApplicationContract()), address(appContract)); assertEq(address(daveConsensus.getTournamentFactory()), address(_tournamentFactory)); + assertEq(daveConsensus.getClaimStagingPeriod(), claimStagingPeriod); assertEq(daveConsensus.getDeploymentBlockNumber(), vm.getBlockNumber()); assertTrue(daveConsensus.supportsInterface(type(IERC165).interfaceId)); assertTrue(daveConsensus.supportsInterface(type(IOutputsMerkleRootValidator).interfaceId)); @@ -680,12 +978,20 @@ contract DaveAppFactoryTest is Test { } } - function _randomizeBlockNumber() internal { + function _randomizeBlockNumber(uint64 claimStagingPeriod) internal { // We limit the block number by type(uint64).max because the PRT contracts // use block numbers for time-keeping, and stores them as uint64 values. - // We also give some slack (the maximum tournament allowance) so we can - // fast-forward to a block in which the tournament is closed. - vm.roll(vm.randomUint(vm.getBlockNumber(), type(uint64).max - Time.Duration.unwrap(MAX_ALLOWANCE))); + // We assume there is some slack so we can fast-forward to a block in which + // the tournament is closed, and we can stage the tournament result, and a + // block in which the staged tournament result can be accepted. + // We type the claim staging period as uint64 because otherwise the fuzzer + // would often pick values too high for these assumptions. + uint256 blockNumber = vm.getBlockNumber(); + uint64 maxAllowance = Time.Duration.unwrap(MAX_ALLOWANCE); + vm.assume(blockNumber <= type(uint256).max - maxAllowance); + vm.assume(blockNumber + maxAllowance <= type(uint256).max - claimStagingPeriod); + vm.assume(blockNumber + maxAllowance + claimStagingPeriod <= type(uint64).max); + vm.roll(blockNumber + vm.randomUint(0, type(uint64).max - maxAllowance - claimStagingPeriod)); } function _randomProof(uint256 n) internal returns (bytes32[] memory proof) { @@ -749,4 +1055,34 @@ contract DaveAppFactoryTest is Test { { return abi.encodeWithSelector(IDaveConsensus.IncorrectEpochNumber.selector, received, actual); } + + function _encodeInvalidOutputsMerkleRootProofSize(uint256 suppliedProofSize) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector(IDaveConsensus.InvalidOutputsMerkleRootProofSize.selector, suppliedProofSize); + } + + function _encodeInvalidOutputsMerkleRootProof(bytes32 machineMerkleRoot) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector(IDaveConsensus.InvalidOutputsMerkleRootProof.selector, machineMerkleRoot); + } + + function _encodeClaimStagingPeriodNotOverYet(uint256 numberOfBlocksAfterStaging, uint256 claimStagingPeriod) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector( + IDaveConsensus.ClaimStagingPeriodNotOverYet.selector, numberOfBlocksAfterStaging, claimStagingPeriod + ); + } + + function _encodeApplicationForeclosed(address appContract) internal pure returns (bytes memory encodedError) { + return abi.encodeWithSelector(IApplicationChecker.ApplicationForeclosed.selector, appContract); + } } diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index e618dc022..ede3f3669 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -5,6 +5,7 @@ use alloy::{ node_bindings::{Anvil, AnvilInstance}, primitives::Address, primitives::FixedBytes, + primitives::U256, providers::{DynProvider, Provider, ProviderBuilder}, signers::{Signer, local::PrivateKeySigner}, }; @@ -88,6 +89,8 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .expect("failed to read machine root hash") }; + let claim_staging_period = U256::from(0); + let withdrawal_config = WithdrawalConfig { guardian: Default::default(), log2LeavesPerAccount: Default::default(), @@ -100,7 +103,12 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let dave_app_factory_contract = IDaveAppFactory::new(dave_app_factory, &provider); let (app, consensus) = dave_app_factory_contract - .calculateDaveAppAddress(initial_hash.into(), withdrawal_config.clone(), salt) + .calculateDaveAppAddress( + initial_hash.into(), + claim_staging_period, + withdrawal_config.clone(), + salt, + ) .call() .await .expect("failed to calculate Dave app addresses") @@ -108,7 +116,12 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .unwrap(); dave_app_factory_contract - .newDaveApp(initial_hash.into(), withdrawal_config.clone(), salt) + .newDaveApp( + initial_hash.into(), + claim_staging_period, + withdrawal_config.clone(), + salt, + ) .send() .await? .watch() diff --git a/cartesi-rollups/node/epoch-manager/src/lib.rs b/cartesi-rollups/node/epoch-manager/src/lib.rs index 43106e715..4dd514357 100644 --- a/cartesi-rollups/node/epoch-manager/src/lib.rs +++ b/cartesi-rollups/node/epoch-manager/src/lib.rs @@ -68,15 +68,28 @@ impl EpochManager { alloy::network::Ethereum, >, ) -> Result<()> { - let can_settle = dave_consensus - .canSettle() + self.try_stage_tournament_result(dave_consensus).await?; + self.try_accept_staged_tournament_result(dave_consensus) + .await?; + Ok(()) + } + + async fn try_stage_tournament_result( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let can_stage = dave_consensus + .canStageTournamentResult() .block(alloy::eips::BlockId::pending()) .call() .await?; - if can_settle.isFinished { + if can_stage.isFinished && !can_stage.isTournamentResultStaged { match self.state_manager.settlement_info( - can_settle + can_stage .epochNumber .to_u64() .expect("fail to convert epoch number to u64"), @@ -84,30 +97,76 @@ impl EpochManager { Some(settlement) => { assert_eq!( settlement.computation_hash.data(), - can_settle.winnerCommitment, + can_stage.winnerCommitment, "Winner commitment mismatch, notify all users!" ); info!( - "settle epoch {} with claim {}", - can_settle.epochNumber, + "stage tournament result of epoch {} with claim {}", + can_stage.epochNumber, settlement.computation_hash.to_hex() ); let tx_result = dave_consensus - .settle( - can_settle.epochNumber, + .stageTournamentResult( + can_stage.epochNumber, vec_u8_to_bytes_32(settlement.output_merkle.into()), to_bytes_32_vec(settlement.output_proof), ) .send() .await; - allow_revert_rethrow_others("settle", tx_result).await?; + allow_revert_rethrow_others("stageTournamentResult", tx_result).await?; + } + None => { + trace!("wait for the `machine-runner` to insert the value"); + } + } + } else { + trace!("tournament result not ready to be staged"); + } + Ok(()) + } + + async fn try_accept_staged_tournament_result( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let can_accept = dave_consensus + .canAcceptStagedTournamentResult() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if can_accept.isTournamentResultStaged && can_accept.isClaimStagingPeriodOver { + match self.state_manager.settlement_info( + can_accept + .epochNumber + .to_u64() + .expect("fail to convert epoch number to u64"), + )? { + Some(settlement) => { + assert_eq!( + vec_u8_to_bytes_32(settlement.output_merkle.into()), + can_accept.stagedPostEpochOutputsMerkleRoot, + "Staged outputs Merkle root mismatch, notify all users!" + ); + info!( + "accept staged tournament result of epoch {}", + can_accept.epochNumber + ); + let tx_result = dave_consensus + .acceptStagedTournamentResult(can_accept.epochNumber) + .send() + .await; + allow_revert_rethrow_others("acceptStagedTournamentResult", tx_result).await?; } None => { trace!("wait for the `machine-runner` to insert the value"); } } } else { - trace!("epoch not ready to be settled"); + trace!("staged tournament result not ready to be accepted"); } Ok(()) } diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 6b8402719..80e85834f 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -280,10 +280,11 @@ function Reader:balance(address) end function Reader:calculate_dave_app_address(template_hash, salt) - local sig = "calculateDaveAppAddress(bytes32,(address,uint8,uint8,uint64,address),bytes32)(address,address)" + local sig = "calculateDaveAppAddress(bytes32,uint256,(address,uint8,uint8,uint64,address),bytes32)(address,address)" + local claim_staging_period = 0 local address_zero = "0x" .. string.rep("00", 20) local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) - local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, withdrawal_config, salt }) + local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, claim_staging_period, withdrawal_config, salt }) assert(#ret == 2) return table.unpack(ret) end diff --git a/prt/tests/rollups/dave/sender.lua b/prt/tests/rollups/dave/sender.lua index 9482d26ff..a674a9381 100644 --- a/prt/tests/rollups/dave/sender.lua +++ b/prt/tests/rollups/dave/sender.lua @@ -113,13 +113,14 @@ function Sender:tx_add_inputs(inputs) end function Sender:tx_new_dave_app(template_hash, salt) - local sig = "newDaveApp(bytes32,(address,uint8,uint8,uint64,address),bytes32)" + local sig = "newDaveApp(bytes32,uint256,(address,uint8,uint8,uint64,address),bytes32)" + local claim_staging_period = 0 local address_zero = "0x" .. string.rep("00", 20) local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) return self:_send_tx( self.dave_app_factory_address, sig, - { template_hash, withdrawal_config, salt } + { template_hash, claim_staging_period, withdrawal_config, salt } ) end From 604de208fc71483cbfd1d88ac0a813213318e54c Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Thu, 9 Jul 2026 19:34:40 -0300 Subject: [PATCH 026/113] feat!: add sentries to DaveConsensus - This commit alters the database schema to include the final state of the machine on the settlement_info table. This is a breaking change which will require nodes to run from a brand-new database. --- .../contracts/src/DaveAppFactory.sol | 22 +- .../contracts/src/DaveConsensus.sol | 100 ++++++- .../contracts/src/IDaveAppFactory.sol | 15 +- .../contracts/src/IDaveConsensus.sol | 93 +++++- .../contracts/src/ISentryErrors.sol | 20 ++ .../contracts/test/DaveAppFactory.t.sol | 273 ++++++++++++++++-- .../node/blockchain-reader/src/test_utils.rs | 7 +- .../node/cartesi-rollups-prt-node/src/lib.rs | 1 + cartesi-rollups/node/epoch-manager/src/lib.rs | 125 +++++++- cartesi-rollups/node/state-manager/src/lib.rs | 1 + .../src/persistent_state_access.rs | 17 +- .../node/state-manager/src/sql/migrations.sql | 1 + .../node/state-manager/src/sql/rollup_data.rs | 22 +- prt/client-rs/core/src/tournament/sender.rs | 9 +- prt/tests/rollups/dave/node.lua | 2 + prt/tests/rollups/dave/reader.lua | 13 +- prt/tests/rollups/dave/sender.lua | 9 +- prt/tests/rollups/test_env.lua | 5 +- 18 files changed, 649 insertions(+), 86 deletions(-) create mode 100644 cartesi-rollups/contracts/src/ISentryErrors.sol diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index df53067c4..14edb906b 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -37,11 +37,12 @@ contract DaveAppFactory is IDaveAppFactory { function newDaveApp( bytes32 templateHash, uint256 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external override returns (IApplication appContract, IDaveConsensus daveConsensus) { appContract = _newApplication(templateHash, withdrawalConfig, salt); - daveConsensus = _newDaveConsensus(address(appContract), templateHash, claimStagingPeriod, salt); + daveConsensus = _newDaveConsensus(address(appContract), templateHash, claimStagingPeriod, sentries, salt); appContract.migrateToOutputsMerkleRootValidator(daveConsensus); appContract.renounceOwnership(); emit DaveAppCreated(appContract, daveConsensus); @@ -50,12 +51,13 @@ contract DaveAppFactory is IDaveAppFactory { function calculateDaveAppAddress( bytes32 templateHash, uint256 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external view override returns (address appContractAddress, address daveConsensusAddress) { appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt); daveConsensusAddress = - _calculateDaveConsensusAddress(appContractAddress, templateHash, claimStagingPeriod, salt); + _calculateDaveConsensusAddress(appContractAddress, templateHash, claimStagingPeriod, sentries, salt); } /// @notice Encode the data availability blob for applications that only use the input box as DA. @@ -78,13 +80,16 @@ contract DaveAppFactory is IDaveAppFactory { } /// @notice Instantiate a new `DaveConsensus` contract. - function _newDaveConsensus(address appContract, bytes32 templateHash, uint256 claimStagingPeriod, bytes32 salt) - internal - returns (DaveConsensus) - { + function _newDaveConsensus( + address appContract, + bytes32 templateHash, + uint256 claimStagingPeriod, + address[] calldata sentries, + bytes32 salt + ) internal returns (DaveConsensus) { Machine.Hash initialMachineStateHash = Machine.Hash.wrap(templateHash); return new DaveConsensus{salt: salt}( - INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash, claimStagingPeriod + INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash, claimStagingPeriod, sentries ); } @@ -105,6 +110,7 @@ contract DaveAppFactory is IDaveAppFactory { address appContract, bytes32 templateHash, uint256 claimStagingPeriod, + address[] calldata sentries, bytes32 salt ) internal view returns (address) { return Create2.computeAddress( @@ -112,7 +118,7 @@ contract DaveAppFactory is IDaveAppFactory { keccak256( abi.encodePacked( type(DaveConsensus).creationCode, - abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash, claimStagingPeriod) + abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash, claimStagingPeriod, sentries) ) ) ); diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index 54cab71fb..751c05888 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.8; import {ERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol"; +import {BitMaps} from "@openzeppelin-contracts-5.2.0/utils/structs/BitMaps.sol"; import { IOutputsMerkleRootValidator @@ -29,6 +30,7 @@ import {IDaveConsensus} from "./IDaveConsensus.sol"; contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { using LibMath for uint256; + using BitMaps for BitMaps.BitMap; using LibBinaryMerkleTree for bytes; using LibBinaryMerkleTree for bytes32[]; @@ -47,6 +49,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { /// @notice Deployment block number uint256 immutable _DEPLOYMENT_BLOCK_NUMBER = block.number; + /// @notice The total number of sentries. + /// @notice See the `getNumberOfSentries` function. + uint256 immutable _NUM_OF_SENTRIES; + /// @notice Current sealed epoch number uint256 _epochNumber; @@ -80,18 +86,45 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { /// @notice Last-finalized machine state hash Machine.Hash _lastFinalizedMachineStateHash; + /// @notice Sentry IDs indexed by address. + /// @notice See the `getSentryId` function. + /// @dev Non-sentries are assigned to ID zero. + /// @dev Sentries have IDs greater than zero. + mapping(address => uint256) private _sentryId; + + /// @notice Sentry addresses indexed by ID. + /// @notice See the `getSentryById` function. + /// @dev Invalid IDs map to address zero. + mapping(uint256 => address) private _sentryById; + + /// @notice A mapping that keeps track of which sentries have claimed in any given epoch. + /// @notice See the `hasSentryClaimedInEpoch` function. + mapping(uint256 => BitMaps.BitMap) private _epochClaimBitMap; + + /// @notice A mapping that keeps track of post-epoch machine state hash claim counts per epoch. + /// @notice See the `getSentryClaimCount` function. + mapping(uint256 => mapping(Machine.Hash => uint256)) private _claimCount; + constructor( IInputBox inputBox, address appContract, ITournamentFactory tournamentFactory, Machine.Hash initialMachineStateHash, - uint256 claimStagingPeriod + uint256 claimStagingPeriod, + address[] memory sentries ) { // Initialize immutable variables _INPUT_BOX = inputBox; _APP_CONTRACT = appContract; _TOURNAMENT_FACTORY = tournamentFactory; _CLAIM_STAGING_PERIOD = claimStagingPeriod; + for (uint256 i; i < sentries.length; ++i) { + address sentry = sentries[i]; + _ensureSentryAddressIsValid(sentry); + uint256 sentryId = ++_NUM_OF_SENTRIES; + _sentryId[sentry] = sentryId; + _sentryById[sentryId] = sentry; + } emit ConsensusCreation(inputBox, appContract, tournamentFactory); // Initialize first sealed epoch @@ -150,12 +183,38 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { emit EpochStaged(epochNumber, finalMachineStateHash, outputsMerkleRoot); } + function submitSentryClaim(uint256 epochNumber, Machine.Hash postEpochMachineStateHash) + external + override + notForeclosed(_APP_CONTRACT) + { + // Check whether caller is authorized + address caller = msg.sender; + uint256 sentryId = getSentryId(caller); + require(sentryId > 0, CallerIsNotSentry(caller)); + + // Check epoch settlement + require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber)); + + // Check whether sentry has claimed in epoch already + BitMaps.BitMap storage epochClaimBitMap = _epochClaimBitMap[epochNumber]; + require(!epochClaimBitMap.get(sentryId), SentryAlreadyClaimed(epochNumber, sentryId)); + + // Mark epoch as claimed (for sentry) and increment claim count for post-epoch state hash + epochClaimBitMap.set(sentryId); + ++_claimCount[epochNumber][postEpochMachineStateHash]; + + // Emit sentry claim event so that off-chain components can update their tallies + emit SentryClaim(epochNumber, sentryId, caller, postEpochMachineStateHash); + } + function canAcceptStagedTournamentResult() external view override returns ( bool isTournamentResultStaged, + bool doAllSentriesAgreeWithStagedTournamentResult, bool isClaimStagingPeriodOver, uint256 epochNumber, Machine.Hash stagedPostEpochMachineStateHash, @@ -165,6 +224,7 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { epochNumber = _epochNumber; isTournamentResultStaged = _isTournamentResultStaged; if (_isTournamentResultStaged) { + doAllSentriesAgreeWithStagedTournamentResult = _doAllSentriesAgreeWithStagedTournamentResult(); isClaimStagingPeriodOver = ((block.number - _stagingBlockNumber) >= _CLAIM_STAGING_PERIOD); stagedPostEpochMachineStateHash = _stagedPostEpochMachineStateHash; stagedPostEpochOutputsMerkleRoot = _stagedPostEpochOutputsMerkleRoot; @@ -179,7 +239,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { require(_isTournamentResultStaged, TournamentResultNotStaged()); // Check whether the claim staging period has elapsed - { + // if not all sentries agree with the staged tournament result + if (!_doAllSentriesAgreeWithStagedTournamentResult()) { uint256 numberOfBlocksAfterStaging = block.number - _stagingBlockNumber; require( numberOfBlocksAfterStaging >= _CLAIM_STAGING_PERIOD, @@ -255,6 +316,31 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { return _CLAIM_STAGING_PERIOD; } + function getNumberOfSentries() external view override returns (uint256) { + return _NUM_OF_SENTRIES; + } + + function getSentryId(address sentry) public view override returns (uint256) { + return _sentryId[sentry]; + } + + function getSentryById(uint256 sentryId) external view override returns (address) { + return _sentryById[sentryId]; + } + + function hasSentryClaimedInEpoch(uint256 epochNumber, uint256 sentryId) external view override returns (bool) { + return _epochClaimBitMap[epochNumber].get(sentryId); + } + + function getSentryClaimCount(uint256 epochNumber, Machine.Hash postEpochMachineStateHash) + external + view + override + returns (uint256) + { + return _claimCount[epochNumber][postEpochMachineStateHash]; + } + function provideMerkleRootOfInput(uint256 inputIndexWithinEpoch, bytes calldata input) external view @@ -323,6 +409,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { require(machineStateHash == allegedStateHash, InvalidOutputsMerkleRootProof(finalMachineStateHash)); } + function _doAllSentriesAgreeWithStagedTournamentResult() internal view returns (bool) { + return _NUM_OF_SENTRIES > 0 && _claimCount[_epochNumber][_stagedPostEpochMachineStateHash] == _NUM_OF_SENTRIES; + } + modifier onlyValidAppContract(address appContract) { _ensureAppContractIsValid(appContract); _; @@ -331,4 +421,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { function _ensureAppContractIsValid(address appContract) internal view { require(_APP_CONTRACT == appContract, ApplicationMismatch(_APP_CONTRACT, appContract)); } + + function _ensureSentryAddressIsValid(address sentry) internal view { + require(sentry != address(0), ZeroSentryAddress()); + uint256 sentryId = getSentryId(sentry); + require(sentryId == 0, DuplicatedSentryAddress(sentryId, sentry)); + } } diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index 12cc10587..25e59fd5e 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -8,11 +8,12 @@ import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicatio import {IApplicationFactoryErrors} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactoryErrors.sol"; import {IDaveConsensus} from "./IDaveConsensus.sol"; +import {ISentryErrors} from "./ISentryErrors.sol"; /// @title Dave-App Pair Factory /// @notice Allows anyone to reliably deploy an application /// validated by a newly-deployed `IDaveConsensus` contract. -interface IDaveAppFactory is IApplicationFactoryErrors { +interface IDaveAppFactory is IApplicationFactoryErrors, ISentryErrors { /// @notice A Dave-App pair was created. /// @param appContract The application contract /// @param daveConsensus The Dave consensus contract @@ -21,13 +22,23 @@ interface IDaveAppFactory is IApplicationFactoryErrors { /// @notice Deploy a new Dave-App pair deterministically. /// @param templateHash The application template hash /// @param claimStagingPeriod The claim staging period + /// @param sentries The array of sentries /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContract The application contract /// @return daveConsensus The Dave consensus contract + /// @dev May raise `ZeroSentryAddress` and `DuplicatedSentryAddress` errors, + /// if the sentry array contains a zero or duplicated address, respectively. + /// If an empty sentries array is provided, then epochs only settle + /// after tournament results are staged for `claimStagingPeriod` blocks. + /// If a non-empty sentries array is provided, then the claim staging period + /// serves as a fallback if not all sentries agree with the tournament result, + /// which should give the guardian enough time to foreclose the application + /// if the disagreement stems from a bug on PRT. function newDaveApp( bytes32 templateHash, uint256 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external returns (IApplication appContract, IDaveConsensus daveConsensus); @@ -35,6 +46,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors { /// @notice Calculate the address of a Dave-App pair. /// @param templateHash The application template hash /// @param claimStagingPeriod The claim staging period + /// @param sentries The array of sentries /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses /// @return appContractAddress The application contract address @@ -42,6 +54,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors { function calculateDaveAppAddress( bytes32 templateHash, uint256 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external view returns (address appContractAddress, address daveConsensusAddress); diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 1b1b2af7f..5eb02968f 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -17,6 +17,8 @@ import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; import {Tree} from "prt-contracts/types/Tree.sol"; +import {ISentryErrors} from "./ISentryErrors.sol"; + /// @notice Consensus contract with Dave tournaments. /// /// @notice This contract validates only one application, @@ -32,6 +34,8 @@ import {Tree} from "prt-contracts/types/Tree.sol"; /// settled already and which one is open for challenges still. /// Anyone can stage a tournament result by calling `stageTournamentResult`. /// One can also check if it can be staged by calling `canStageTournamentResult`. +/// Sentries can submit claims for the post-epoch machine state hash +/// in order to speed up epoch settlement (by skipping the claim staging period). /// Anyone can settle an epoch by calling `acceptStagedTournamentResult`. /// One can also check if it can be settled by calling `canAcceptStagedTournamentResult`. /// @@ -43,9 +47,16 @@ import {Tree} from "prt-contracts/types/Tree.sol"; /// Every sealed epoch has an associated tournament. /// Once a tournament is finished, and a winner commitment is declared, /// it can then be staged by anyone. After the claim staging period is elapsed, +/// or all sentries (if there is any) agree with the staged tournament result, /// anyone can settle the epoch by accepting the staged winner commitment. /// -interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplicationChecker, BinaryMerkleTreeErrors { +interface IDaveConsensus is + IDataProvider, + IOutputsMerkleRootValidator, + IApplicationChecker, + BinaryMerkleTreeErrors, + ISentryErrors +{ /// @notice Consensus contract was created /// @param inputBox the input box contract /// @param appContract the application contract @@ -68,6 +79,18 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica ITournament tournament ); + /// @notice A sentry has claimed the post-epoch machine state hash. + /// @param epochNumber The epoch number + /// @param sentryId The sentry ID + /// @param sentry The sentry address + /// @param postEpochMachineStateHash The post-epoch machine state hash + event SentryClaim( + uint256 indexed epochNumber, + uint256 indexed sentryId, + address indexed sentry, + Machine.Hash postEpochMachineStateHash + ); + /// @notice An epoch was staged /// @param epochNumber the sealed epoch number /// @param stagedPostEpochMachineStateHash The staged post-epoch machine state hash @@ -113,6 +136,17 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica /// @param received Received application address error ApplicationMismatch(address expected, address received); + /// @notice This error is raised whenever the `submitSentryClaim` + /// function is called by someone who is not a sentry. + /// @param caller The caller address + error CallerIsNotSentry(address caller); + + /// @notice This error is raised whenever a sentry attempts to call the + /// `submitSentryClaim` function twice for the same epoch. + /// @param epochNumber The epoch number + /// @param sentryId The sentry ID + error SentryAlreadyClaimed(uint256 epochNumber, uint256 sentryId); + /// @notice Get the number of base-layer block in which the contract was deployed. function getDeploymentBlockNumber() external view returns (uint256); @@ -127,10 +161,44 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica /// @notice Get the number of base-layer blocks after which a staged claim can be accepted. /// @dev A claim, in the context of PRT, is the winner commitment of a tournament, if there is one. - /// Once a tournament finishes, and a winner is declared, anyone can stage the tournament result, - /// and, after the claim staging period is elapsed, accept it into finality. + /// Once a tournament finishes, and a winner is declared, anyone can stage the tournament result. + /// After all sentries agree with the staged claim or after the claim staging period is elapsed, + /// anyone can accept the staged claim into finality. If there are no sentries, then acceptance + /// can only occur after the claim staging period is elapsed. function getClaimStagingPeriod() external view returns (uint256); + /// @notice Get the number of sentries. + /// @dev This number can be zero, that is, there are no sentries. + function getNumberOfSentries() external view returns (uint256); + + /// @notice Get the ID of a sentry. + /// @param sentry The sentry address + /// @dev Sentries are assigned IDs between 1 and `N`, the total number of sentries. + /// @dev Non-sentries are assigned to ID zero. + function getSentryId(address sentry) external view returns (uint256); + + /// @notice Get the address of a sentry by its ID. + /// @param sentryId The sentry ID + /// @dev Sentry IDs range from 1 to `N`, the total number of sentries. + /// @dev Valid IDs do not map to address zero. + /// @dev Invalid IDs map to address zero. + function getSentryById(uint256 sentryId) external view returns (address); + + /// @notice Check whether a sentry has claimed any post-epoch machine state in a given epoch. + /// @param epochNumber The epoch number + /// @param sentryId The sentry ID + /// @dev You can obtain the ID of a sentry by its address through the `getSentryId` function + /// or the address of a sentry by its ID through the `getSentryById` function. + function hasSentryClaimedInEpoch(uint256 epochNumber, uint256 sentryId) external view returns (bool); + + /// @notice Get the number of sentries that have claimed a given post-epoch machine state in a given epoch. + /// @param epochNumber The epoch number + /// @param postEpochMachineStateHash The post-epoch machine state hash + function getSentryClaimCount(uint256 epochNumber, Machine.Hash postEpochMachineStateHash) + external + view + returns (uint256); + /// @notice Get the current sealed epoch number, boundaries, tournament, and staging info. /// @return epochNumber The epoch number /// @return inputIndexLowerBound The epoch input index (inclusive) lower bound @@ -181,21 +249,32 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica /// @dev On success, emits an `EpochStaged` event. function stageTournamentResult(uint256 epochNumber, bytes32 outputsMerkleRoot, bytes32[] calldata proof) external; + /// @notice As a sentry, claim the post-epoch machine state hash for the current sealed epoch. + /// If all sentries claim the same post-epoch machine state hash as the staged tournament result, + /// then the claim staging period is skipped entirely, potentially shortening the finality delay. + /// Note that this skipping is only possible if the consensus has at least one sentry. + /// @param epochNumber The current sealed epoch number (used to avoid race conditions) + /// @param postEpochMachineStateHash The post-epoch machine state hash + /// @dev On success, emits a `SentryClaim` event. + function submitSentryClaim(uint256 epochNumber, Machine.Hash postEpochMachineStateHash) external; + /// @notice Check whether the staged tournament result of the current sealed epoch can be accepted. /// @return isTournamentResultStaged Whether the tournament result (if there is one) is staged + /// @return doAllSentriesAgreeWithStagedTournamentResult Whether all sentries agree with staged tournament result /// @return isClaimStagingPeriodOver Whether the claim staging period is over /// @return epochNumber The current sealed epoch number /// @return stagedPostEpochMachineStateHash If the tournament result is staged, the staged post-epoch machine state hash /// @return stagedPostEpochOutputsMerkleRoot If the tournament result is staged, the staged post-epoch outputs Merkle root - /// @dev Validators should only call `acceptStagedTournamentResult` if both isTournamentResultStaged - /// and isClaimStagingPeriodOver are true. Be also mindful that isClaimStagingPeriodOver, - /// stagedPostEpochMachineStateHash, and stagedPostEpochOutputsMerkleRoot only have any meaning - /// if isTournamentResultStaged is true. + /// @dev Validators should only call `acceptStagedTournamentResult` if the Boolean expression isTournamentResultStaged + /// AND (doAllSentriesAgreeWithStagedTournamentResult OR isClaimStagingPeriodOver) is true. Be also mindful that + /// doAllSentriesAgreeWithStagedTournamentResult, isClaimStagingPeriodOver, stagedPostEpochMachineStateHash, and + /// stagedPostEpochOutputsMerkleRoot only have any meaning if isTournamentResultStaged is true. function canAcceptStagedTournamentResult() external view returns ( bool isTournamentResultStaged, + bool doAllSentriesAgreeWithStagedTournamentResult, bool isClaimStagingPeriodOver, uint256 epochNumber, Machine.Hash stagedPostEpochMachineStateHash, diff --git a/cartesi-rollups/contracts/src/ISentryErrors.sol b/cartesi-rollups/contracts/src/ISentryErrors.sol new file mode 100644 index 000000000..163277d27 --- /dev/null +++ b/cartesi-rollups/contracts/src/ISentryErrors.sol @@ -0,0 +1,20 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pragma solidity ^0.8.8; + +interface ISentryErrors { + /// @notice This error is raised either when one tries to deploy a DaveConsensus + /// contract or when the sentry manager tries to rotate a sentry. This is forbidden + /// because the zero address is reserved as a sentinel value for non-sentries (when + /// calling the `getSentryById` function with an invalid sentry ID). + error ZeroSentryAddress(); + + /// @notice This error is raised either when one tries to deploy a DaveConsensus + /// contract or when the sentry manager tries to rotate a sentry. This is forbidden + /// because each sentry address should be assigned a single unique sentry ID (which + /// can be retrieved by calling the `getSentryId` function with the sentry address). + /// @param sentryId The sentry ID + /// @param sentry The sentry address + error DuplicatedSentryAddress(uint256 sentryId, address sentry); +} diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index 4868d6356..d672ecd59 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -46,6 +46,7 @@ import {Tree} from "prt-contracts/types/Tree.sol"; import {DaveAppFactory} from "src/DaveAppFactory.sol"; import {IDaveAppFactory} from "src/IDaveAppFactory.sol"; import {IDaveConsensus} from "src/IDaveConsensus.sol"; +import {ISentryErrors} from "src/ISentryErrors.sol"; library LibExternalBinaryKeccak256MerkleTree { using LibBinaryMerkleTree for bytes32[]; @@ -89,17 +90,18 @@ contract DaveAppFactoryTest is Test { function testNewDaveApp( bytes32 templateHash, uint64 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external { _randomizeBlockNumber(claimStagingPeriod); (address precalculatedAppContractAddress, address precalculatedDaveConsensusAddress) = - _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, withdrawalConfig, salt); + _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt); vm.recordLogs(); - try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt) returns ( + try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt) returns ( IApplication appContract, IDaveConsensus daveConsensus ) { Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -116,10 +118,14 @@ contract DaveAppFactoryTest is Test { "calculateDaveAppAddress(...)[1] != newDaveApp(...)[1]" ); - _testNewDaveAppSuccess(templateHash, claimStagingPeriod, withdrawalConfig, appContract, daveConsensus, logs); + _testNewDaveAppSuccess( + templateHash, claimStagingPeriod, sentries, withdrawalConfig, appContract, daveConsensus, logs + ); (precalculatedAppContractAddress, precalculatedDaveConsensusAddress) = - _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, withdrawalConfig, salt); + _daveAppFactory.calculateDaveAppAddress( + templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt + ); assertEq( precalculatedAppContractAddress, @@ -133,12 +139,12 @@ contract DaveAppFactoryTest is Test { "calculateDaveAppAddress(...)[1] != newDaveApp(...)[1]" ); } catch (bytes memory errorData) { - _testNewDaveAppFailure(withdrawalConfig, errorData); + _testNewDaveAppFailure(sentries, withdrawalConfig, errorData); return; } // Cannot deploy an application with the same salt twice - try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt) { + try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt) { revert("second deterministic deployment did not revert"); } catch (bytes memory errorData) { assertEq( @@ -150,6 +156,7 @@ contract DaveAppFactoryTest is Test { function testStageAndAcceptTournamentResult( bytes32 templateHash, uint64 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt, bytes32 outputsMerkleRoot, @@ -162,7 +169,7 @@ contract DaveAppFactoryTest is Test { vm.assumeNoRevert(); (appContract, daveConsensus) = - _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, withdrawalConfig, salt); + _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt); bytes[] memory inputs = new bytes[](inputPayloads.length); @@ -285,7 +292,7 @@ contract DaveAppFactoryTest is Test { bool val1; uint256 val2; - (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + (val1,,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); assertFalse(val1); // isTournamentResultStaged assertEq(val2, 0); // epochNumber @@ -352,7 +359,7 @@ contract DaveAppFactoryTest is Test { bool val1; uint256 val2; - (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + (val1,,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); assertFalse(val1); // isTournamentResultStaged assertEq(val2, 0); // epochNumber @@ -472,17 +479,19 @@ contract DaveAppFactoryTest is Test { { bool val1; bool val2; - uint256 val3; - Machine.Hash val4; - bytes32 val5; + bool val3; + uint256 val4; + Machine.Hash val5; + bytes32 val6; - (val1, val2, val3, val4, val5) = daveConsensus.canAcceptStagedTournamentResult(); + (val1, val2, val3, val4, val5, val6) = daveConsensus.canAcceptStagedTournamentResult(); assertTrue(val1); // isTournamentResultStaged - assertEq(val2, claimStagingPeriod == 0); // isClaimStagingPeriodOver - assertEq(val3, 0); // epochNumber - assertEq(Machine.Hash.unwrap(val4), machineMerkleRoot); - assertEq(val5, outputsMerkleRoot); + assertFalse(val2); // doAllSentriesAgreeWithStagedTournamentResult + assertEq(val3, claimStagingPeriod == 0); // isClaimStagingPeriodOver + assertEq(val4, 0); // epochNumber + assertEq(Machine.Hash.unwrap(val5), machineMerkleRoot); + assertEq(val6, outputsMerkleRoot); } assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); @@ -502,6 +511,17 @@ contract DaveAppFactoryTest is Test { daveConsensus.acceptStagedTournamentResult(0); } + // If there is at least one sentry, at random decide to submit sentry claims + // corroborating with the staged tournament result + uint256 numOfSentries = daveConsensus.getNumberOfSentries(); + uint256[] memory sentryIds = _getShuffledSentryIds(numOfSentries); + uint256 numOfClaims = vm.randomUint(0, numOfSentries); + bool doAllSentriesAgreeWithStagedTournamentResult = (numOfSentries > 0) && (numOfClaims == numOfSentries); + for (uint256 i; i < numOfClaims; ++i) { + _submitSentryClaim(appContract, daveConsensus, 0, sentryIds[i], Machine.Hash.wrap(machineMerkleRoot)); + _attemptSentryClaimResubmission(daveConsensus, 0, sentryIds[vm.randomUint(0, i)]); + } + vm.roll(vm.randomUint(stagingBlockNumber + claimStagingPeriod, type(uint64).max)); // Check current sealed epoch @@ -548,17 +568,19 @@ contract DaveAppFactoryTest is Test { { bool val1; bool val2; - uint256 val3; - Machine.Hash val4; - bytes32 val5; + bool val3; + uint256 val4; + Machine.Hash val5; + bytes32 val6; - (val1, val2, val3, val4, val5) = daveConsensus.canAcceptStagedTournamentResult(); + (val1, val2, val3, val4, val5, val6) = daveConsensus.canAcceptStagedTournamentResult(); assertTrue(val1); // isTournamentResultStaged - assertTrue(val2); // isClaimStagingPeriodOver - assertEq(val3, 0); // epochNumber - assertEq(Machine.Hash.unwrap(val4), machineMerkleRoot); - assertEq(val5, outputsMerkleRoot); + assertEq(val2, doAllSentriesAgreeWithStagedTournamentResult); + assertTrue(val3); // isClaimStagingPeriodOver + assertEq(val4, 0); // epochNumber + assertEq(Machine.Hash.unwrap(val5), machineMerkleRoot); + assertEq(val6, outputsMerkleRoot); } assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); @@ -615,7 +637,7 @@ contract DaveAppFactoryTest is Test { bool val1; uint256 val2; - (val1,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); + (val1,,, val2,,) = daveConsensus.canAcceptStagedTournamentResult(); assertFalse(val1); // isTournamentResultStaged assertEq(val2, 1); // epochNumber @@ -699,6 +721,23 @@ contract DaveAppFactoryTest is Test { revert("Successful staging"); } + /// @notice This function is used to simulate a foreclosure and a sentry claim. + /// If the claim succeeds, then the function reverts with error message "Successful claim". + /// If the claim fails, then the function propagates the error from the DaveConsensus contract. + function simulateForeclosureAndSentryClaim( + IApplication appContract, + IDaveConsensus daveConsensus, + uint256 epochNumber, + address sentry, + Machine.Hash postEpochMachineStateHash + ) external { + vm.prank(appContract.getGuardian()); + appContract.foreclose(); + vm.prank(sentry); + daveConsensus.submitSentryClaim(epochNumber, postEpochMachineStateHash); + revert("Successful claim"); + } + /// @notice This function is used to simulate a foreclosure and a tournament-result acceptance. /// If the acceptance succeeds, then the function reverts with error message "Successful acceptance". /// If the acceptance fails, then the function propagates the error from the DaveConsensus contract. @@ -717,6 +756,7 @@ contract DaveAppFactoryTest is Test { function _testNewDaveAppSuccess( bytes32 templateHash, uint64 claimStagingPeriod, + address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, IApplication appContract, IDaveConsensus daveConsensus, @@ -921,6 +961,7 @@ contract DaveAppFactoryTest is Test { assertEq(address(daveConsensus.getApplicationContract()), address(appContract)); assertEq(address(daveConsensus.getTournamentFactory()), address(_tournamentFactory)); assertEq(daveConsensus.getClaimStagingPeriod(), claimStagingPeriod); + assertEq(_getSentries(daveConsensus), sentries); assertEq(daveConsensus.getDeploymentBlockNumber(), vm.getBlockNumber()); assertTrue(daveConsensus.supportsInterface(type(IERC165).interfaceId)); assertTrue(daveConsensus.supportsInterface(type(IOutputsMerkleRootValidator).interfaceId)); @@ -965,14 +1006,34 @@ contract DaveAppFactoryTest is Test { bytes memory input = vm.randomBytes(inputLength); assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexWithinBounds, input), bytes32(0)); } + + assertEq(daveConsensus.getSentryId(address(0)), 0); + assertEq(daveConsensus.getSentryId(_randomAddressNotIn(sentries)), 0); + assertEq(daveConsensus.getSentryById(0), address(0)); + assertEq(daveConsensus.getSentryById(vm.randomUint(sentries.length + 1, type(uint256).max)), address(0)); + assertFalse(daveConsensus.hasSentryClaimedInEpoch(vm.randomUint(), vm.randomUint())); + assertEq(daveConsensus.getSentryClaimCount(vm.randomUint(), Machine.Hash.wrap(bytes32(vm.randomUint()))), 0); } - function _testNewDaveAppFailure(WithdrawalConfig calldata withdrawalConfig, bytes memory errorData) internal pure { + function _testNewDaveAppFailure( + address[] calldata sentries, + WithdrawalConfig calldata withdrawalConfig, + bytes memory errorData + ) internal pure { (bool isValidError, bytes32 errorSelector, bytes memory errorArgs) = errorData.consumeBytes4(); assertTrue(isValidError, "Expected error to contain a 4-byte selector"); if (errorSelector == IApplicationFactoryErrors.InvalidWithdrawalConfig.selector) { assertEq(errorArgs, abi.encode(withdrawalConfig), "Expected withdrawal configs to match"); assertFalse(withdrawalConfig.isValid(), "Expected withdrawal config to be invalid"); + } else if (errorSelector == ISentryErrors.ZeroSentryAddress.selector) { + assertEq(errorArgs, abi.encode()); + assertTrue(_contains(sentries, address(0)), "Expected sentries array to have the zero address"); + } else if (errorSelector == ISentryErrors.DuplicatedSentryAddress.selector) { + (uint256 sentryId, address sentry) = abi.decode(errorArgs, (uint256, address)); + assertGe(sentryId, 1, "Expected sentry ID >= 1"); + assertLe(sentryId, sentries.length, "Expected sentry ID <= N"); + assertEq(sentries[sentryId - 1], sentry, "Expected array to have sentry at given index"); + assertTrue(_contains(sentries[sentryId:], sentry), "Expected array to have duplicated address"); } else { revert("Unexpected error"); } @@ -1040,6 +1101,85 @@ contract DaveAppFactoryTest is Test { } } + function _submitSentryClaim( + IApplication appContract, + IDaveConsensus daveConsensus, + uint256 epochNumber, + uint256 sentryId, + Machine.Hash postEpochMachineStateHash + ) internal { + address sentry = daveConsensus.getSentryById(sentryId); + + // Pick a random hash for testing error cases + Machine.Hash randomHash = Machine.Hash.wrap(bytes32(vm.randomUint())); + + // Attempt to claim a random hash for the wrong epoch number + uint256 randomEpochNumber = _randomUintNotEq(epochNumber); + vm.prank(sentry); + vm.expectRevert(_encodeIncorrectEpochNumber(randomEpochNumber, epochNumber)); + daveConsensus.submitSentryClaim(randomEpochNumber, randomHash); + + // Make a non-sentry attempt to claim a random hash + address nonSentry = _randomNonSentry(daveConsensus); + vm.prank(nonSentry); + vm.expectRevert(_encodeCallerIsNotSentry(nonSentry)); + daveConsensus.submitSentryClaim(epochNumber, randomHash); + + // Simulate foreclosure and attempt to claim a random hash + vm.expectRevert(_encodeApplicationForeclosed(address(appContract))); + this.simulateForeclosureAndSentryClaim(appContract, daveConsensus, epochNumber, sentry, randomHash); + + // Ensure the sentry has not submitted a claim yet + assertFalse(daveConsensus.hasSentryClaimedInEpoch(epochNumber, sentryId)); + + // Get the current number of claims before the submission for later comparison + uint256 claimCountBefore = daveConsensus.getSentryClaimCount(epochNumber, postEpochMachineStateHash); + assertLe(claimCountBefore, daveConsensus.getNumberOfSentries()); + + // Make the sentry submit the claim while recording logs + vm.recordLogs(); + vm.prank(sentry); + daveConsensus.submitSentryClaim(epochNumber, postEpochMachineStateHash); + + // Check the logs for a SentryClaim event + Vm.Log[] memory logs = vm.getRecordedLogs(); + uint256 numOfSentryClaimEvents; + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(daveConsensus)) { + assertGe(log.topics.length, 1); + bytes32 topic0 = log.topics[0]; + if (topic0 == IDaveConsensus.SentryClaim.selector) { + assertEq(log.topics.length, 4); + assertEq(log.topics[1], bytes32(epochNumber)); + assertEq(log.topics[2], bytes32(sentryId)); + assertEq(log.topics[3], bytes32(uint256(uint160(sentry)))); + assertEq(log.data, abi.encode(postEpochMachineStateHash)); + ++numOfSentryClaimEvents; + } else { + revert UnexpectedLogTopic0(log); + } + } else { + revert UnexpectedLogEmitter(log); + } + } + assertEq(numOfSentryClaimEvents, 1); + + // Ensure the sentry has claimed in epoch according to the contract and that + // the number of claims in the epoch increased by 1 + assertTrue(daveConsensus.hasSentryClaimedInEpoch(epochNumber, sentryId)); + assertEq(daveConsensus.getSentryClaimCount(epochNumber, postEpochMachineStateHash), claimCountBefore + 1); + } + + function _attemptSentryClaimResubmission(IDaveConsensus daveConsensus, uint256 epochNumber, uint256 sentryId) + internal + { + address randomSentry = daveConsensus.getSentryById(sentryId); + vm.expectRevert(_encodeSentryAlreadyClaimed(epochNumber, sentryId)); + vm.prank(randomSentry); + daveConsensus.submitSentryClaim(epochNumber, Machine.Hash.wrap(bytes32(vm.randomUint()))); + } + function _encodeApplicationMismatch(address expected, address obtained) internal pure @@ -1085,4 +1225,83 @@ contract DaveAppFactoryTest is Test { function _encodeApplicationForeclosed(address appContract) internal pure returns (bytes memory encodedError) { return abi.encodeWithSelector(IApplicationChecker.ApplicationForeclosed.selector, appContract); } + + function _encodeSentryAlreadyClaimed(uint256 epochNumber, uint256 sentryId) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector(IDaveConsensus.SentryAlreadyClaimed.selector, epochNumber, sentryId); + } + + function _encodeCallerIsNotSentry(address caller) internal pure returns (bytes memory encodedError) { + return abi.encodeWithSelector(IDaveConsensus.CallerIsNotSentry.selector, caller); + } + + function _randomUintNotEq(uint256 n) internal returns (uint256 m) { + while (true) { + m = vm.randomUint(); + if (n != m) { + break; + } + } + } + + function _contains(address[] memory array, address value) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == value) { + return true; + } + } + return false; + } + + function _randomAddressNotIn(address[] memory disallowList) internal returns (address addr) { + while (true) { + addr = vm.randomAddress(); + if (!_contains(disallowList, addr)) { + break; + } + } + } + + function _randomNonSentry(IDaveConsensus daveConsensus) internal returns (address nonSentry) { + while (true) { + nonSentry = vm.randomAddress(); + if (daveConsensus.getSentryId(nonSentry) == 0) { + break; + } + } + } + + function _getSentries(IDaveConsensus daveConsensus) internal view returns (address[] memory sentries) { + sentries = new address[](daveConsensus.getNumberOfSentries()); + for (uint256 i; i < sentries.length; ++i) { + uint256 sentryId = i + 1; + sentries[i] = daveConsensus.getSentryById(sentryId); + assertEq(daveConsensus.getSentryId(sentries[i]), sentryId); + assertNotEq(sentries[i], address(0)); + } + } + + function _getShuffledSentryIds(uint256 numOfSentries) internal returns (uint256[] memory sentryIds) { + sentryIds = new uint256[](numOfSentries); + for (uint256 i; i < numOfSentries; ++i) { + sentryIds[i] = i + 1; + } + _shuffleInPlace(sentryIds); + } + + function _shuffleInPlace(uint256[] memory array) internal { + // Nothing to be done. + if (array.length == 0) { + return; + } + + // Fisher-Yates shuffle + for (uint256 i = array.length - 1; i > 0; --i) { + uint256 j = vm.randomUint(0, i); + (array[i], array[j]) = (array[j], array[i]); + } + } } diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index ede3f3669..4e457f87c 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -64,6 +64,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let mut signer: PrivateKeySigner = anvil.keys()[0].clone().into(); signer.set_chain_id(Some(anvil.chain_id())); + let signer_address = signer.address(); let wallet = EthereumWallet::from(signer); let provider = ProviderBuilder::new() @@ -89,7 +90,9 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .expect("failed to read machine root hash") }; - let claim_staging_period = U256::from(0); + let claim_staging_period = U256::from(1000); + + let sentries = vec![signer_address]; let withdrawal_config = WithdrawalConfig { guardian: Default::default(), @@ -106,6 +109,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .calculateDaveAppAddress( initial_hash.into(), claim_staging_period, + sentries.clone(), withdrawal_config.clone(), salt, ) @@ -119,6 +123,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .newDaveApp( initial_hash.into(), claim_staging_period, + sentries.clone(), withdrawal_config.clone(), salt, ) diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs b/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs index 647fb362c..25eed47f0 100644 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs +++ b/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs @@ -95,6 +95,7 @@ pub fn create_epoch_manager_task(watch: Watch, parameters: &PRTConfig) -> thread let epoch_manager = EpochManager::new( Arc::new(Mutex::new(arena_sender)), params.address_book.consensus, + params.signer_address, state_manager, params.sleep_duration, params.long_block_range_error_codes.clone(), diff --git a/cartesi-rollups/node/epoch-manager/src/lib.rs b/cartesi-rollups/node/epoch-manager/src/lib.rs index 4dd514357..78c219fdf 100644 --- a/cartesi-rollups/node/epoch-manager/src/lib.rs +++ b/cartesi-rollups/node/epoch-manager/src/lib.rs @@ -24,6 +24,7 @@ use rollups_state_manager::{Epoch, Proof, StateManager, sync::Watch}; pub struct EpochManager { arena_sender: Arc>, consensus: Address, + signer_address: Address, sleep_duration: Duration, long_block_range_error_codes: Vec, state_manager: SM, @@ -34,6 +35,7 @@ impl EpochManager { pub fn new( arena_sender: Arc>, consensus_address: Address, + signer_address: Address, state_manager: SM, sleep_duration: Duration, long_block_range_error_codes: Vec, @@ -41,6 +43,7 @@ impl EpochManager { Self { arena_sender, consensus: consensus_address, + signer_address, sleep_duration, long_block_range_error_codes, state_manager, @@ -68,9 +71,93 @@ impl EpochManager { alloy::network::Ethereum, >, ) -> Result<()> { + self.try_submit_sentry_claim(dave_consensus).await?; self.try_stage_tournament_result(dave_consensus).await?; - self.try_accept_staged_tournament_result(dave_consensus) + self.try_accept_tournament_result(dave_consensus).await?; + Ok(()) + } + + async fn try_submit_sentry_claim( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let sentry_id = dave_consensus + .getSentryId(self.signer_address) + .block(alloy::eips::BlockId::pending()) + .call() .await?; + + if sentry_id == 0 { + trace!( + "signer {} is not a sentry of DaveConsensus@{}", + self.signer_address, + dave_consensus.address() + ); + return Ok(()); + } + + let current_sealed_epoch = dave_consensus + .getCurrentSealedEpoch() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + let epoch_number = current_sealed_epoch.epochNumber; + + let has_voted = dave_consensus + .hasSentryClaimedInEpoch(epoch_number, sentry_id) + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if has_voted { + trace!( + "sentry {} (id {}) has already voted for epoch {} of DaveConsensus@{}", + self.signer_address, + sentry_id, + epoch_number, + dave_consensus.address() + ); + return Ok(()); + } + + let can_accept = dave_consensus + .canAcceptStagedTournamentResult() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if can_accept.isTournamentResultStaged && can_accept.isClaimStagingPeriodOver { + trace!( + "epoch {} already has a staged tournament result past its staging period", + epoch_number + ); + return Ok(()); + } + + match self.state_manager.settlement_info( + epoch_number + .to_u64() + .expect("fail to convert epoch number to u64"), + )? { + Some(settlement) => { + let claim = vec_u8_to_bytes_32(settlement.final_state.into()); + + info!("submit sentry claim {} for epoch {}", claim, epoch_number); + let tx_result = dave_consensus + .submitSentryClaim(epoch_number, claim) + .send() + .await; + + allow_revert_rethrow_others("submitSentryClaim", tx_result).await?; + } + None => { + trace!("wait for the `machine-runner` to insert the value"); + } + } Ok(()) } @@ -100,6 +187,10 @@ impl EpochManager { can_stage.winnerCommitment, "Winner commitment mismatch, notify all users!" ); + assert_eq!( + settlement.final_state, can_stage.winnerPostEpochMachineStateHash, + "Winner final state mismatch, notify all users!" + ); info!( "stage tournament result of epoch {} with claim {}", can_stage.epochNumber, @@ -125,7 +216,7 @@ impl EpochManager { Ok(()) } - async fn try_accept_staged_tournament_result( + async fn try_accept_tournament_result( &mut self, dave_consensus: &DaveConsensus::DaveConsensusInstance< DynProvider, @@ -138,7 +229,7 @@ impl EpochManager { .call() .await?; - if can_accept.isTournamentResultStaged && can_accept.isClaimStagingPeriodOver { + if can_accept.isTournamentResultStaged { match self.state_manager.settlement_info( can_accept .epochNumber @@ -146,20 +237,30 @@ impl EpochManager { .expect("fail to convert epoch number to u64"), )? { Some(settlement) => { + assert_eq!( + vec_u8_to_bytes_32(settlement.final_state.into()), + can_accept.stagedPostEpochMachineStateHash, + "Staged final state mismatch, notify all users!" + ); assert_eq!( vec_u8_to_bytes_32(settlement.output_merkle.into()), can_accept.stagedPostEpochOutputsMerkleRoot, "Staged outputs Merkle root mismatch, notify all users!" ); - info!( - "accept staged tournament result of epoch {}", - can_accept.epochNumber - ); - let tx_result = dave_consensus - .acceptStagedTournamentResult(can_accept.epochNumber) - .send() - .await; - allow_revert_rethrow_others("acceptStagedTournamentResult", tx_result).await?; + if can_accept.doAllSentriesAgreeWithStagedTournamentResult + || can_accept.isClaimStagingPeriodOver + { + info!( + "accept staged tournament result of epoch {}", + can_accept.epochNumber + ); + let tx_result = dave_consensus + .acceptStagedTournamentResult(can_accept.epochNumber) + .send() + .await; + allow_revert_rethrow_others("acceptStagedTournamentResult", tx_result) + .await?; + } } None => { trace!("wait for the `machine-runner` to insert the value"); diff --git a/cartesi-rollups/node/state-manager/src/lib.rs b/cartesi-rollups/node/state-manager/src/lib.rs index 442afc825..bb83317d2 100644 --- a/cartesi-rollups/node/state-manager/src/lib.rs +++ b/cartesi-rollups/node/state-manager/src/lib.rs @@ -65,6 +65,7 @@ impl Proof { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Settlement { pub computation_hash: Digest, + pub final_state: Hash, pub output_merkle: Hash, pub output_proof: Proof, } diff --git a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs index 704d1a601..f49a85c76 100644 --- a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs +++ b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs @@ -12,6 +12,7 @@ use crate::{ use alloy::primitives::U256; use cartesi_dave_merkle::{Digest, MerkleBuilder}; +use cartesi_machine::types::Hash; use rusqlite::Connection; #[derive(Debug)] @@ -212,7 +213,7 @@ impl StateManager for PersistentStateAccess { let settlement = { let leafs = rollup_data::get_all_commitments(&self.connection, previous_epoch_number)?; - let computation_hash = if !leafs.is_empty() { + let (computation_hash, final_state) = if !leafs.is_empty() { build_commitment_from_hashes(&leafs) } else { assert_eq!(machine.next_input_index_in_epoch(), 0); @@ -226,6 +227,7 @@ impl StateManager for PersistentStateAccess { Settlement { computation_hash, + final_state, output_merkle, output_proof, } @@ -286,7 +288,7 @@ impl StateManager for PersistentStateAccess { } } -fn build_commitment_from_hashes(state_hashes: &[CommitmentLeaf]) -> Digest { +fn build_commitment_from_hashes(state_hashes: &[CommitmentLeaf]) -> (Digest, Hash) { let mut builder = MerkleBuilder::default(); assert!(!state_hashes.is_empty()); @@ -306,7 +308,7 @@ fn build_commitment_from_hashes(state_hashes: &[CommitmentLeaf]) -> Digest { ); let tree = builder.build(); - tree.root_hash() + (tree.root_hash(), last.hash) } #[cfg(test)] @@ -526,13 +528,14 @@ mod tests { access.roll_epoch()?; assert_eq!(access.latest_snapshot()?.epoch(), 1); + let (computation_hash, final_state) = + build_commitment_from_hashes(&[commitment_leaf_1.clone(), commitment_leaf_2.clone()]); + assert_eq!( access.settlement_info(0)?.unwrap(), Settlement { - computation_hash: build_commitment_from_hashes(&[ - commitment_leaf_1.clone(), - commitment_leaf_2.clone() - ]), + computation_hash, + final_state, output_merkle, output_proof }, diff --git a/cartesi-rollups/node/state-manager/src/sql/migrations.sql b/cartesi-rollups/node/state-manager/src/sql/migrations.sql index e14e5fa96..9006e6531 100644 --- a/cartesi-rollups/node/state-manager/src/sql/migrations.sql +++ b/cartesi-rollups/node/state-manager/src/sql/migrations.sql @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS settlement_info ( epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), computation_hash BLOB NOT NULL, + final_state BLOB NOT NULL, output_merkle BLOB NOT NULL, output_proof BLOB NOT NULL ); diff --git a/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs b/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs index 0b9be5d27..4ae0002f7 100644 --- a/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs +++ b/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs @@ -29,16 +29,21 @@ fn convert_row_to_settlement(row: &rusqlite::Row) -> rusqlite::Result [u8;32] + let fs_blob: Vec = row.get(1)?; + let final_state: Hash = fs_blob.try_into().expect("final_state must be 32 bytes"); + // output_merkle blob -> [u8;32] - let om_blob: Vec = row.get(1)?; + let om_blob: Vec = row.get(2)?; let output_merkle: Hash = om_blob.try_into().expect("output_merkle must be 32 bytes"); // output_proof blob -> Proof - let proof_blob: Vec = row.get(2)?; + let proof_blob: Vec = row.get(3)?; let output_proof = Proof::from_flattened(proof_blob); Ok(Settlement { computation_hash, + final_state, output_merkle, output_proof, }) @@ -108,7 +113,7 @@ pub fn settlement_info(conn: &Connection, epoch_number: u64) -> Result B256 { (*h).into() }) .collect(); trace!( - "final state for tournament {} at position {}", - proof.node, proof.position + "join tournament {:?} with final_state {} at position {}, left {}, right {}, proof_len {}", + tournament, + proof.node, + proof.position, + left_child, + right_child, + proof.siblings.len() ); let tx_result = tournament .joinTournament( diff --git a/prt/tests/rollups/dave/node.lua b/prt/tests/rollups/dave/node.lua index b1d828e1d..bb97c44a1 100644 --- a/prt/tests/rollups/dave/node.lua +++ b/prt/tests/rollups/dave/node.lua @@ -4,6 +4,7 @@ local Machine = require "computation.machine" local helper = require "utils.helper" local time = require "utils.time" +local ANVIL_ADDRESS_7 = "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" local ANVIL_KEY_7 = "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356" local function start_dave_node(machine_path, app_address, db_path, sleep_duration, verbosity, trace_level) @@ -32,6 +33,7 @@ end local Dave = {} Dave.__index = Dave +Dave.wallet_address = ANVIL_ADDRESS_7 function Dave:new(machine_path, app_address, sender, sleep_duration, verbosity, trace_level) -- trace, debug, info, warn, error diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 80e85834f..64bfcbcc8 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -64,7 +64,7 @@ end local Reader = {} Reader.__index = Reader -function Reader:new(input_box_address, dave_app_factory_address, template_hash, salt, endpoint, genesis) +function Reader:new(input_box_address, dave_app_factory_address, template_hash, sentries, salt, endpoint, genesis) genesis = genesis or 0 endpoint = endpoint or blockchain_constants.endpoint local reader = { @@ -78,7 +78,7 @@ function Reader:new(input_box_address, dave_app_factory_address, template_hash, setmetatable(reader, self) -- pre-calculate app and consensus addresses based on provided template hash and salt values - reader.app_address, reader.consensus_address = reader:calculate_dave_app_address(template_hash, salt) + reader.app_address, reader.consensus_address = reader:calculate_dave_app_address(template_hash, sentries, salt) return reader end @@ -279,12 +279,13 @@ function Reader:balance(address) return uint256.new(balance) end -function Reader:calculate_dave_app_address(template_hash, salt) - local sig = "calculateDaveAppAddress(bytes32,uint256,(address,uint8,uint8,uint64,address),bytes32)(address,address)" - local claim_staging_period = 0 +function Reader:calculate_dave_app_address(template_hash, sentries, salt) + local sig = "calculateDaveAppAddress(bytes32,uint256,address[],(address,uint8,uint8,uint64,address),bytes32)(address,address)" + local claim_staging_period = 1000 + local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local address_zero = "0x" .. string.rep("00", 20) local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) - local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, claim_staging_period, withdrawal_config, salt }) + local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, claim_staging_period, sentries_str, withdrawal_config, salt }) assert(#ret == 2) return table.unpack(ret) end diff --git a/prt/tests/rollups/dave/sender.lua b/prt/tests/rollups/dave/sender.lua index a674a9381..8c753c75b 100644 --- a/prt/tests/rollups/dave/sender.lua +++ b/prt/tests/rollups/dave/sender.lua @@ -112,15 +112,16 @@ function Sender:tx_add_inputs(inputs) end end -function Sender:tx_new_dave_app(template_hash, salt) - local sig = "newDaveApp(bytes32,uint256,(address,uint8,uint8,uint64,address),bytes32)" - local claim_staging_period = 0 +function Sender:tx_new_dave_app(template_hash, sentries, salt) + local sig = "newDaveApp(bytes32,uint256,address[],(address,uint8,uint8,uint64,address),bytes32)" + local claim_staging_period = 1000 + local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local address_zero = "0x" .. string.rep("00", 20) local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) return self:_send_tx( self.dave_app_factory_address, sig, - { template_hash, claim_staging_period, withdrawal_config, salt } + { template_hash, claim_staging_period, sentries_str, withdrawal_config, salt } ) end diff --git a/prt/tests/rollups/test_env.lua b/prt/tests/rollups/test_env.lua index a6d6a2fd7..437b68328 100644 --- a/prt/tests/rollups/test_env.lua +++ b/prt/tests/rollups/test_env.lua @@ -56,13 +56,14 @@ function Env.spawn_blockchain(inputs) local blockchain = Blockchain:new(ANVIL_LOAD_PATH, ANVIL_DUMP_PATH) Env.blockchain = blockchain - Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, SALT, blockchain + Env.sentries = {Dave.wallet_address} + Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, Env.sentries, SALT, blockchain .endpoint) Env.app_address = Env.reader.app_address Env.consensus_address = Env.reader.consensus_address Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], blockchain.endpoint) - Env.sender:tx_new_dave_app(TEMPLATE_MACHINE_HASH, SALT) + Env.sender:tx_new_dave_app(TEMPLATE_MACHINE_HASH, Env.sentries, SALT) Env.sender:tx_add_inputs(inputs) Env.sender:advance_blocks(2) return blockchain From 82b576c0122b3b564336eaf497b032a4c89d08b1 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Fri, 10 Jul 2026 17:52:10 -0300 Subject: [PATCH 027/113] feat!: add sentry rotation to DaveConsensus --- .../contracts/src/DaveAppFactory.sol | 30 ++- .../contracts/src/DaveConsensus.sol | 35 ++++ .../contracts/src/IDaveAppFactory.sol | 4 + .../contracts/src/IDaveConsensus.sol | 41 ++++ .../contracts/test/DaveAppFactory.t.sol | 196 ++++++++++++++++-- .../node/blockchain-reader/src/test_utils.rs | 4 + prt/tests/rollups/dave/reader.lua | 7 +- prt/tests/rollups/dave/sender.lua | 7 +- 8 files changed, 297 insertions(+), 27 deletions(-) diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index 14edb906b..b25beef41 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -37,12 +37,14 @@ contract DaveAppFactory is IDaveAppFactory { function newDaveApp( bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external override returns (IApplication appContract, IDaveConsensus daveConsensus) { appContract = _newApplication(templateHash, withdrawalConfig, salt); - daveConsensus = _newDaveConsensus(address(appContract), templateHash, claimStagingPeriod, sentries, salt); + daveConsensus = + _newDaveConsensus(address(appContract), templateHash, claimStagingPeriod, sentryManager, sentries, salt); appContract.migrateToOutputsMerkleRootValidator(daveConsensus); appContract.renounceOwnership(); emit DaveAppCreated(appContract, daveConsensus); @@ -51,13 +53,15 @@ contract DaveAppFactory is IDaveAppFactory { function calculateDaveAppAddress( bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external view override returns (address appContractAddress, address daveConsensusAddress) { appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt); - daveConsensusAddress = - _calculateDaveConsensusAddress(appContractAddress, templateHash, claimStagingPeriod, sentries, salt); + daveConsensusAddress = _calculateDaveConsensusAddress( + appContractAddress, templateHash, claimStagingPeriod, sentryManager, sentries, salt + ); } /// @notice Encode the data availability blob for applications that only use the input box as DA. @@ -84,12 +88,19 @@ contract DaveAppFactory is IDaveAppFactory { address appContract, bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, bytes32 salt ) internal returns (DaveConsensus) { Machine.Hash initialMachineStateHash = Machine.Hash.wrap(templateHash); return new DaveConsensus{salt: salt}( - INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash, claimStagingPeriod, sentries + INPUT_BOX, + appContract, + TOURNAMENT_FACTORY, + initialMachineStateHash, + claimStagingPeriod, + sentryManager, + sentries ); } @@ -110,6 +121,7 @@ contract DaveAppFactory is IDaveAppFactory { address appContract, bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, bytes32 salt ) internal view returns (address) { @@ -118,7 +130,15 @@ contract DaveAppFactory is IDaveAppFactory { keccak256( abi.encodePacked( type(DaveConsensus).creationCode, - abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash, claimStagingPeriod, sentries) + abi.encode( + INPUT_BOX, + appContract, + TOURNAMENT_FACTORY, + templateHash, + claimStagingPeriod, + sentryManager, + sentries + ) ) ) ); diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index 751c05888..a2fa7e309 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -49,6 +49,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { /// @notice Deployment block number uint256 immutable _DEPLOYMENT_BLOCK_NUMBER = block.number; + /// @notice The account that is authorized to manage sentry rotations. + /// @notice See the `getSentryManager` function. + address immutable _SENTRY_MANAGER; + /// @notice The total number of sentries. /// @notice See the `getNumberOfSentries` function. uint256 immutable _NUM_OF_SENTRIES; @@ -111,6 +115,7 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { ITournamentFactory tournamentFactory, Machine.Hash initialMachineStateHash, uint256 claimStagingPeriod, + address sentryManager, address[] memory sentries ) { // Initialize immutable variables @@ -118,6 +123,7 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { _APP_CONTRACT = appContract; _TOURNAMENT_FACTORY = tournamentFactory; _CLAIM_STAGING_PERIOD = claimStagingPeriod; + _SENTRY_MANAGER = sentryManager; for (uint256 i; i < sentries.length; ++i) { address sentry = sentries[i]; _ensureSentryAddressIsValid(sentry); @@ -273,6 +279,21 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { ); } + function rotateSentry(address currentSentry, address newSentry) + external + override + onlySentryManager + notForeclosed(_APP_CONTRACT) + { + uint256 sentryId = getSentryId(currentSentry); + require(sentryId > 0, CannotRotateNonSentry(currentSentry)); + _ensureSentryAddressIsValid(newSentry); + _sentryId[currentSentry] = 0; + _sentryId[newSentry] = sentryId; + _sentryById[sentryId] = newSentry; + emit SentryRotation(sentryId, currentSentry, newSentry); + } + function getCurrentSealedEpoch() external view @@ -316,6 +337,10 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { return _CLAIM_STAGING_PERIOD; } + function getSentryManager() external view override returns (address) { + return _SENTRY_MANAGER; + } + function getNumberOfSentries() external view override returns (uint256) { return _NUM_OF_SENTRIES; } @@ -418,6 +443,11 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { _; } + modifier onlySentryManager() { + _ensureCallerIsSentryManager(); + _; + } + function _ensureAppContractIsValid(address appContract) internal view { require(_APP_CONTRACT == appContract, ApplicationMismatch(_APP_CONTRACT, appContract)); } @@ -427,4 +457,9 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { uint256 sentryId = getSentryId(sentry); require(sentryId == 0, DuplicatedSentryAddress(sentryId, sentry)); } + + function _ensureCallerIsSentryManager() internal view { + address caller = msg.sender; + require(caller == _SENTRY_MANAGER, CallerIsNotSentryManager(caller)); + } } diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index 25e59fd5e..ccc1f7524 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -22,6 +22,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors, ISentryErrors { /// @notice Deploy a new Dave-App pair deterministically. /// @param templateHash The application template hash /// @param claimStagingPeriod The claim staging period + /// @param sentryManager The sentry manager address /// @param sentries The array of sentries /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses @@ -38,6 +39,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors, ISentryErrors { function newDaveApp( bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt @@ -46,6 +48,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors, ISentryErrors { /// @notice Calculate the address of a Dave-App pair. /// @param templateHash The application template hash /// @param claimStagingPeriod The claim staging period + /// @param sentryManager The sentry manager address /// @param sentries The array of sentries /// @param withdrawalConfig The withdrawal configuration /// @param salt A 32-byte value used to add entropy to the addresses @@ -54,6 +57,7 @@ interface IDaveAppFactory is IApplicationFactoryErrors, ISentryErrors { function calculateDaveAppAddress( bytes32 templateHash, uint256 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 5eb02968f..252daf429 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -99,6 +99,17 @@ interface IDaveConsensus is uint256 epochNumber, Machine.Hash stagedPostEpochMachineStateHash, bytes32 stagedPostEpochOutputsMerkleRoot ); + /// @notice The sentry manager rotated a sentry. + /// @param sentryId The sentry ID + /// @param oldSentry The old sentry address + /// @param newSentry The new sentry address + /// @dev It is guaranteed that, in the instant right before the rotation, + /// `getSentryId(oldSentry) == sentryId`, `getSentryId(newSentry) == 0`, and `getSentryById(sentryId) == oldSentry`. + /// And, in the instant right after the rotation, it is also guaranteed that + /// `getSentryId(oldSentry) == 0`, `getSentryId(newSentry) == sentryId`, and `getSentryById(sentryId) == newSentry`. + /// Furthermore, the number of sentries is unchanged all other sentries keep their IDs and addresses. + event SentryRotation(uint256 indexed sentryId, address indexed oldSentry, address indexed newSentry); + /// @notice Received epoch number is different from actual /// @param received The epoch number received as argument /// @param actual The actual epoch number in storage @@ -147,6 +158,16 @@ interface IDaveConsensus is /// @param sentryId The sentry ID error SentryAlreadyClaimed(uint256 epochNumber, uint256 sentryId); + /// @notice This error is raised whenever the `rotateSentry` + /// function is called by someone who is not the sentry manager. + /// @param caller The caller address + error CallerIsNotSentryManager(address caller); + + /// @notice This error is raised whenever the `rotateSentry` + /// function is called with a non-sentry address as `currentSentry`. + /// @param nonSentry The non-sentry address + error CannotRotateNonSentry(address nonSentry); + /// @notice Get the number of base-layer block in which the contract was deployed. function getDeploymentBlockNumber() external view returns (uint256); @@ -171,6 +192,10 @@ interface IDaveConsensus is /// @dev This number can be zero, that is, there are no sentries. function getNumberOfSentries() external view returns (uint256); + /// @notice Get the sentry manager address. + /// @dev The sentry manager is the only one capable of rotating sentries. + function getSentryManager() external view returns (address); + /// @notice Get the ID of a sentry. /// @param sentry The sentry address /// @dev Sentries are assigned IDs between 1 and `N`, the total number of sentries. @@ -199,6 +224,22 @@ interface IDaveConsensus is view returns (uint256); + /// @notice As a sentry manager, rotate a sentry. + /// @param currentSentry The current sentry address + /// @param newSentry The new sentry address that will inherit the current sentry's ID + /// @dev This action can be useful whenever a sentry account is compromised. + /// A compromised sentry may either purposefully submit false post-epoch machine state hashes + /// or drain the funds from the sentry account. In either case, the epoch settlement is delayed + /// by the claim staging period, which is undesired for application users and maintainers. + /// In those cases, the sentry manager can rotate the address of the compromised sentry slot, + /// ensuring epochs settle earlier in the happy path through sentry claims. + /// If a sentry has already claimed in the current sealed epoch, then rotating their address + /// will not erase their claim. Rather, rotation only updates the address that is authorized + /// to claim for that sentry slot. So, if that sentry slot has already placed a claim, then the + /// new sentry will only be able to submit a claim for the next epoch. + /// On success, emits a `SentryRotation` event. + function rotateSentry(address currentSentry, address newSentry) external; + /// @notice Get the current sealed epoch number, boundaries, tournament, and staging info. /// @return epochNumber The epoch number /// @return inputIndexLowerBound The epoch input index (inclusive) lower bound diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index d672ecd59..b50637759 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -90,18 +90,22 @@ contract DaveAppFactoryTest is Test { function testNewDaveApp( bytes32 templateHash, uint64 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) external { _randomizeBlockNumber(claimStagingPeriod); - (address precalculatedAppContractAddress, address precalculatedDaveConsensusAddress) = - _daveAppFactory.calculateDaveAppAddress(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt); + (address precalculatedAppContractAddress, address precalculatedDaveConsensusAddress) = _daveAppFactory.calculateDaveAppAddress( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ); vm.recordLogs(); - try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt) returns ( + try _daveAppFactory.newDaveApp( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ) returns ( IApplication appContract, IDaveConsensus daveConsensus ) { Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -119,12 +123,19 @@ contract DaveAppFactoryTest is Test { ); _testNewDaveAppSuccess( - templateHash, claimStagingPeriod, sentries, withdrawalConfig, appContract, daveConsensus, logs + templateHash, + claimStagingPeriod, + sentryManager, + sentries, + withdrawalConfig, + appContract, + daveConsensus, + logs ); (precalculatedAppContractAddress, precalculatedDaveConsensusAddress) = _daveAppFactory.calculateDaveAppAddress( - templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt ); assertEq( @@ -144,7 +155,9 @@ contract DaveAppFactoryTest is Test { } // Cannot deploy an application with the same salt twice - try _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt) { + try _daveAppFactory.newDaveApp( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ) { revert("second deterministic deployment did not revert"); } catch (bytes memory errorData) { assertEq( @@ -156,6 +169,7 @@ contract DaveAppFactoryTest is Test { function testStageAndAcceptTournamentResult( bytes32 templateHash, uint64 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, bytes32 salt, @@ -168,8 +182,9 @@ contract DaveAppFactoryTest is Test { IDaveConsensus daveConsensus; vm.assumeNoRevert(); - (appContract, daveConsensus) = - _daveAppFactory.newDaveApp(templateHash, claimStagingPeriod, sentries, withdrawalConfig, salt); + (appContract, daveConsensus) = _daveAppFactory.newDaveApp( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ); bytes[] memory inputs = new bytes[](inputPayloads.length); @@ -704,6 +719,110 @@ contract DaveAppFactoryTest is Test { } } + function testRotateSentry( + bytes32 templateHash, + uint64 claimStagingPeriod, + address sentryManager, + address[] calldata sentries, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt + ) external { + IApplication appContract; + IDaveConsensus daveConsensus; + + vm.assumeNoRevert(); + (appContract, daveConsensus) = _daveAppFactory.newDaveApp( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ); + + uint256 numOfSentries = daveConsensus.getNumberOfSentries(); + uint256 numOfRounds = 16; + + for (uint256 round; round < numOfRounds; ++round) { + address nonSentryManager = _randomAddressNotEq(sentryManager); + + vm.prank(nonSentryManager); + vm.expectRevert(_encodeCallerIsNotSentryManager(nonSentryManager)); + daveConsensus.rotateSentry(vm.randomAddress(), vm.randomAddress()); + + vm.expectRevert(_encodeApplicationForeclosed(address(appContract))); + this.simulateForeclosureAndRotation(appContract, daveConsensus, vm.randomAddress(), vm.randomAddress()); + + address nonSentry = _randomNonSentry(daveConsensus); + + vm.prank(sentryManager); + vm.expectRevert(_encodeCannotRotateNonSentry(nonSentry)); + daveConsensus.rotateSentry(nonSentry, vm.randomAddress()); + + if (numOfSentries >= 1) { + uint256 sentryId = vm.randomUint(1, numOfSentries); + address sentry = daveConsensus.getSentryById(sentryId); + assertEq(daveConsensus.getSentryId(sentry), sentryId); + + uint256 anotherSentryId = vm.randomUint(1, numOfSentries); + address anotherSentry = daveConsensus.getSentryById(anotherSentryId); + assertEq(daveConsensus.getSentryId(anotherSentry), anotherSentryId); + + vm.prank(sentryManager); + vm.expectRevert(_encodeDuplicatedSentryAddress(anotherSentryId, anotherSentry)); + daveConsensus.rotateSentry(sentry, anotherSentry); + + vm.prank(sentryManager); + vm.expectRevert(ISentryErrors.ZeroSentryAddress.selector); + daveConsensus.rotateSentry(sentry, address(0)); + + address[] memory sentriesBefore = _getSentries(daveConsensus); + assertEq(sentriesBefore.length, numOfSentries); + + address newSentry = _randomValidNonSentry(daveConsensus); + + vm.recordLogs(); + + vm.prank(sentryManager); + daveConsensus.rotateSentry(sentry, newSentry); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + uint256 numOfSentryRotationEvents; + for (uint256 i; i < logs.length; ++i) { + Vm.Log memory log = logs[i]; + if (log.emitter == address(daveConsensus)) { + assertGe(log.topics.length, 1); + bytes32 topic0 = log.topics[0]; + if (topic0 == IDaveConsensus.SentryRotation.selector) { + assertEq(log.topics.length, 4); + assertEq(log.topics[1], bytes32(sentryId)); + assertEq(log.topics[2], bytes32(uint256(uint160(sentry)))); + assertEq(log.topics[3], bytes32(uint256(uint160(newSentry)))); + assertEq(log.data, abi.encode()); + ++numOfSentryRotationEvents; + } else { + revert UnexpectedLogTopic0(log); + } + } else { + revert UnexpectedLogEmitter(log); + } + } + assertEq(numOfSentryRotationEvents, 1); + + assertEq(daveConsensus.getSentryId(sentry), 0); + assertEq(daveConsensus.getSentryId(newSentry), sentryId); + assertEq(daveConsensus.getSentryById(sentryId), newSentry); + + address[] memory sentriesAfter = _getSentries(daveConsensus); + assertEq(sentriesBefore.length, sentriesAfter.length); + + for (uint256 i; i < sentriesAfter.length; ++i) { + if (sentryId == (i + 1)) { + assertEq(sentriesBefore[i], sentry); + assertEq(sentriesAfter[i], newSentry); + } else { + assertEq(sentriesBefore[i], sentriesAfter[i]); + } + } + } + } + } + /// @notice This function is used to simulate a foreclosure and a tournament-result staging. /// If the staging succeeds, then the function reverts with error message "Successful staging". /// If the staging fails, then the function propagates the error from the DaveConsensus contract. @@ -738,6 +857,22 @@ contract DaveAppFactoryTest is Test { revert("Successful claim"); } + /// @notice This function is used to simulate a foreclosure and a sentry rotation. + /// If the rotation succeeds, then the function reverts with error message "Successful rotation". + /// If the rotation fails, then the function propagates the error from the DaveConsensus contract. + function simulateForeclosureAndRotation( + IApplication appContract, + IDaveConsensus daveConsensus, + address currentSentry, + address newSentry + ) external { + vm.prank(appContract.getGuardian()); + appContract.foreclose(); + vm.prank(daveConsensus.getSentryManager()); + daveConsensus.rotateSentry(currentSentry, newSentry); + revert("Successful rotation"); + } + /// @notice This function is used to simulate a foreclosure and a tournament-result acceptance. /// If the acceptance succeeds, then the function reverts with error message "Successful acceptance". /// If the acceptance fails, then the function propagates the error from the DaveConsensus contract. @@ -756,6 +891,7 @@ contract DaveAppFactoryTest is Test { function _testNewDaveAppSuccess( bytes32 templateHash, uint64 claimStagingPeriod, + address sentryManager, address[] calldata sentries, WithdrawalConfig calldata withdrawalConfig, IApplication appContract, @@ -961,6 +1097,7 @@ contract DaveAppFactoryTest is Test { assertEq(address(daveConsensus.getApplicationContract()), address(appContract)); assertEq(address(daveConsensus.getTournamentFactory()), address(_tournamentFactory)); assertEq(daveConsensus.getClaimStagingPeriod(), claimStagingPeriod); + assertEq(daveConsensus.getSentryManager(), sentryManager); assertEq(_getSentries(daveConsensus), sentries); assertEq(daveConsensus.getDeploymentBlockNumber(), vm.getBlockNumber()); assertTrue(daveConsensus.supportsInterface(type(IERC165).interfaceId)); @@ -970,14 +1107,7 @@ contract DaveAppFactoryTest is Test { assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), bytes32(vm.randomUint()))); - address notAppContract; - - while (true) { - notAppContract = vm.randomAddress(); - if (notAppContract != address(appContract)) { - break; - } - } + address notAppContract = _randomAddressNotEq(address(appContract)); vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); daveConsensus.getLastFinalizedMachineMerkleRoot(notAppContract); @@ -1180,6 +1310,14 @@ contract DaveAppFactoryTest is Test { daveConsensus.submitSentryClaim(epochNumber, Machine.Hash.wrap(bytes32(vm.randomUint()))); } + function _encodeDuplicatedSentryAddress(uint256 sentryId, address sentry) + internal + pure + returns (bytes memory encodedError) + { + return abi.encodeWithSelector(ISentryErrors.DuplicatedSentryAddress.selector, sentryId, sentry); + } + function _encodeApplicationMismatch(address expected, address obtained) internal pure @@ -1238,6 +1376,14 @@ contract DaveAppFactoryTest is Test { return abi.encodeWithSelector(IDaveConsensus.CallerIsNotSentry.selector, caller); } + function _encodeCallerIsNotSentryManager(address caller) internal pure returns (bytes memory encodedError) { + return abi.encodeWithSelector(IDaveConsensus.CallerIsNotSentryManager.selector, caller); + } + + function _encodeCannotRotateNonSentry(address nonSentry) internal pure returns (bytes memory encodedError) { + return abi.encodeWithSelector(IDaveConsensus.CannotRotateNonSentry.selector, nonSentry); + } + function _randomUintNotEq(uint256 n) internal returns (uint256 m) { while (true) { m = vm.randomUint(); @@ -1265,6 +1411,15 @@ contract DaveAppFactoryTest is Test { } } + function _randomAddressNotEq(address n) internal returns (address m) { + while (true) { + m = vm.randomAddress(); + if (n != m) { + break; + } + } + } + function _randomNonSentry(IDaveConsensus daveConsensus) internal returns (address nonSentry) { while (true) { nonSentry = vm.randomAddress(); @@ -1274,6 +1429,15 @@ contract DaveAppFactoryTest is Test { } } + function _randomValidNonSentry(IDaveConsensus daveConsensus) internal returns (address nonSentry) { + while (true) { + nonSentry = _randomNonSentry(daveConsensus); + if (nonSentry != address(0)) { + break; + } + } + } + function _getSentries(IDaveConsensus daveConsensus) internal view returns (address[] memory sentries) { sentries = new address[](daveConsensus.getNumberOfSentries()); for (uint256 i; i < sentries.length; ++i) { diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index 4e457f87c..64568153d 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -92,6 +92,8 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let claim_staging_period = U256::from(1000); + let sentry_manager = Address::ZERO; + let sentries = vec![signer_address]; let withdrawal_config = WithdrawalConfig { @@ -109,6 +111,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .calculateDaveAppAddress( initial_hash.into(), claim_staging_period, + sentry_manager, sentries.clone(), withdrawal_config.clone(), salt, @@ -123,6 +126,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .newDaveApp( initial_hash.into(), claim_staging_period, + sentry_manager, sentries.clone(), withdrawal_config.clone(), salt, diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 64bfcbcc8..07dab5f16 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -280,12 +280,13 @@ function Reader:balance(address) end function Reader:calculate_dave_app_address(template_hash, sentries, salt) - local sig = "calculateDaveAppAddress(bytes32,uint256,address[],(address,uint8,uint8,uint64,address),bytes32)(address,address)" + local sig = "calculateDaveAppAddress(bytes32,uint256,address,address[],(address,uint8,uint8,uint64,address),bytes32)(address,address)" local claim_staging_period = 1000 - local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local address_zero = "0x" .. string.rep("00", 20) + local sentry_manager = address_zero + local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) - local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, claim_staging_period, sentries_str, withdrawal_config, salt }) + local ret = self:_call(self.dave_app_factory_address, sig, { template_hash, claim_staging_period, sentry_manager, sentries_str, withdrawal_config, salt }) assert(#ret == 2) return table.unpack(ret) end diff --git a/prt/tests/rollups/dave/sender.lua b/prt/tests/rollups/dave/sender.lua index 8c753c75b..1325fdf16 100644 --- a/prt/tests/rollups/dave/sender.lua +++ b/prt/tests/rollups/dave/sender.lua @@ -113,15 +113,16 @@ function Sender:tx_add_inputs(inputs) end function Sender:tx_new_dave_app(template_hash, sentries, salt) - local sig = "newDaveApp(bytes32,uint256,address[],(address,uint8,uint8,uint64,address),bytes32)" + local sig = "newDaveApp(bytes32,uint256,address,address[],(address,uint8,uint8,uint64,address),bytes32)" local claim_staging_period = 1000 - local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local address_zero = "0x" .. string.rep("00", 20) + local sentry_manager = address_zero + local sentries_str = "[" .. table.concat(sentries, ",") .. "]" local withdrawal_config = string.format("(%s,0,0,0,%s)", address_zero, address_zero) return self:_send_tx( self.dave_app_factory_address, sig, - { template_hash, claim_staging_period, sentries_str, withdrawal_config, salt } + { template_hash, claim_staging_period, sentry_manager, sentries_str, withdrawal_config, salt } ) end From b839e7ef5ace804c77fda9491a8b967c3c113029 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 13 Jul 2026 11:27:59 -0300 Subject: [PATCH 028/113] chore: bump Solidity pragma to ^0.8.30 --- cartesi-rollups/contracts/script/Deployment.s.sol | 2 +- cartesi-rollups/contracts/src/DaveAppFactory.sol | 2 +- cartesi-rollups/contracts/src/DaveConsensus.sol | 2 +- cartesi-rollups/contracts/src/IDaveAppFactory.sol | 2 +- cartesi-rollups/contracts/src/IDaveConsensus.sol | 2 +- cartesi-rollups/contracts/src/ISentryErrors.sol | 2 +- cartesi-rollups/contracts/test/DaveAppFactory.t.sol | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index a5774a4dc..7de8a6e41 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; import {BaseDeploymentScript} from "prt-contracts/../script/BaseDeploymentScript.sol"; diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index b25beef41..c759905e5 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index a2fa7e309..36615e6c1 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; import {ERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol"; diff --git a/cartesi-rollups/contracts/src/IDaveAppFactory.sol b/cartesi-rollups/contracts/src/IDaveAppFactory.sol index ccc1f7524..1d25e4ffe 100644 --- a/cartesi-rollups/contracts/src/IDaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/IDaveAppFactory.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 252daf429..40cb377be 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; import {BinaryMerkleTreeErrors} from "cartesi-rollups-contracts-3.0.0/src/common/BinaryMerkleTreeErrors.sol"; import { diff --git a/cartesi-rollups/contracts/src/ISentryErrors.sol b/cartesi-rollups/contracts/src/ISentryErrors.sol index 163277d27..a3f44e677 100644 --- a/cartesi-rollups/contracts/src/ISentryErrors.sol +++ b/cartesi-rollups/contracts/src/ISentryErrors.sol @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -pragma solidity ^0.8.8; +pragma solidity ^0.8.30; interface ISentryErrors { /// @notice This error is raised either when one tries to deploy a DaveConsensus diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index b50637759..b08a847d2 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.22; +pragma solidity ^0.8.30; import {Test} from "forge-std-1.9.6/src/Test.sol"; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; From 863b69c5f73df8d6025a8388bc62aaff515077ac Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 13 Jul 2026 12:02:47 -0300 Subject: [PATCH 029/113] feat!: index epochNumber in Epoch{Sealed,Staged} --- .../contracts/src/IDaveConsensus.sol | 6 +- .../contracts/test/DaveAppFactory.t.sol | 62 +++++++++---------- prt/tests/rollups/dave/reader.lua | 12 ++-- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index 40cb377be..3a8d3973f 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -71,7 +71,7 @@ interface IDaveConsensus is /// @param outputsMerkleRoot the Merkle root hash of the outputs tree /// @param tournament the sealed epoch tournament contract event EpochSealed( - uint256 epochNumber, + uint256 indexed epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, Machine.Hash initialMachineStateHash, @@ -96,7 +96,9 @@ interface IDaveConsensus is /// @param stagedPostEpochMachineStateHash The staged post-epoch machine state hash /// @param stagedPostEpochOutputsMerkleRoot The staged post-epoch outputs Merkle root event EpochStaged( - uint256 epochNumber, Machine.Hash stagedPostEpochMachineStateHash, bytes32 stagedPostEpochOutputsMerkleRoot + uint256 indexed epochNumber, + Machine.Hash stagedPostEpochMachineStateHash, + bytes32 stagedPostEpochOutputsMerkleRoot ); /// @notice The sentry manager rotated a sentry. diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index b08a847d2..143af2812 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -431,15 +431,15 @@ contract DaveAppFactoryTest is Test { if (log.topics[0] == IDaveConsensus.EpochStaged.selector) { ++numOfEpochStagedEvents; - uint256 arg1; + assertEq(log.topics[1], bytes32(0)); // epochNumber + + bytes32 arg1; bytes32 arg2; - bytes32 arg3; - (arg1, arg2, arg3) = abi.decode(log.data, (uint256, bytes32, bytes32)); + (arg1, arg2) = abi.decode(log.data, (bytes32, bytes32)); - assertEq(arg1, 0); // epochNumber - assertEq(arg2, machineMerkleRoot); // stagedPostEpochMachineStateHash - assertEq(arg3, outputsMerkleRoot); // stagedPostEpochOutputsMerkleRoot + assertEq(arg1, machineMerkleRoot); // stagedPostEpochMachineStateHash + assertEq(arg2, outputsMerkleRoot); // stagedPostEpochOutputsMerkleRoot } else { revert UnexpectedLogTopic0(log); } @@ -676,22 +676,21 @@ contract DaveAppFactoryTest is Test { if (log.topics[0] == IDaveConsensus.EpochSealed.selector) { ++numOfEpochSealedEvents; + assertEq(log.topics[1], bytes32(uint256(1))); // epochNumber + uint256 arg1; uint256 arg2; - uint256 arg3; + bytes32 arg3; bytes32 arg4; - bytes32 arg5; - address arg6; - - (arg1, arg2, arg3, arg4, arg5, arg6) = - abi.decode(log.data, (uint256, uint256, uint256, bytes32, bytes32, address)); - - assertEq(arg1, 1); // epochNumber - assertEq(arg2, 0); // inputIndexLowerBound - assertEq(arg3, inputs.length); // inputIndexUpperBound - assertEq(arg4, machineMerkleRoot); // initialMachineStateHash - assertEq(arg5, outputsMerkleRoot); - assertEq(arg6, address(tournament)); + address arg5; + + (arg1, arg2, arg3, arg4, arg5) = abi.decode(log.data, (uint256, uint256, bytes32, bytes32, address)); + + assertEq(arg1, 0); // inputIndexLowerBound + assertEq(arg2, inputs.length); // inputIndexUpperBound + assertEq(arg3, machineMerkleRoot); // initialMachineStateHash + assertEq(arg4, outputsMerkleRoot); + assertEq(arg5, address(tournament)); } else { revert UnexpectedLogTopic0(log); } @@ -988,22 +987,21 @@ contract DaveAppFactoryTest is Test { } else if (log.topics[0] == IDaveConsensus.EpochSealed.selector) { ++numOfEpochSealedEvents; + assertEq(log.topics[1], bytes32(0)); // epochNumber + uint256 arg1; uint256 arg2; - uint256 arg3; + bytes32 arg3; bytes32 arg4; - bytes32 arg5; - address arg6; - - (arg1, arg2, arg3, arg4, arg5, arg6) = - abi.decode(log.data, (uint256, uint256, uint256, bytes32, bytes32, address)); - - assertEq(arg1, 0); // epochNumber - assertEq(arg2, 0); // inputIndexLowerBound - assertEq(arg3, 0); // inputIndexUpperBound - assertEq(arg4, templateHash); // initialMachineStateHash - assertEq(arg5, bytes32(0)); // outputsMerkleRoot - assertEq(arg6, address(tournament)); // tournament + address arg5; + + (arg1, arg2, arg3, arg4, arg5) = abi.decode(log.data, (uint256, uint256, bytes32, bytes32, address)); + + assertEq(arg1, 0); // inputIndexLowerBound + assertEq(arg2, 0); // inputIndexUpperBound + assertEq(arg3, templateHash); // initialMachineStateHash + assertEq(arg4, bytes32(0)); // outputsMerkleRoot + assertEq(arg5, address(tournament)); // tournament } else { revert UnexpectedLogTopic0(log); } diff --git a/prt/tests/rollups/dave/reader.lua b/prt/tests/rollups/dave/reader.lua index 07dab5f16..0b9803bd1 100644 --- a/prt/tests/rollups/dave/reader.lua +++ b/prt/tests/rollups/dave/reader.lua @@ -205,7 +205,7 @@ end function Reader:read_epochs_sealed() local sig = "EpochSealed(uint256,uint256,uint256,bytes32,bytes32,address)" - local data_sig = "(uint256,uint256,uint256,bytes32,bytes32,address)" + local data_sig = "(uint256,uint256,bytes32,bytes32,address)" local logs = self:_read_logs(self.consensus_address, sig, { false, false, false }, data_sig) @@ -214,11 +214,11 @@ function Reader:read_epochs_sealed() local log = {} log.meta = v.meta - log.epoch_number = tonumber(v.decoded_data[1]) - log.input_lower_bound = tonumber(v.decoded_data[2]) - log.input_upper_bound = tonumber(v.decoded_data[3]) - log.initial_machine_state_hash = v.decoded_data[4] - log.tournament = v.decoded_data[6] + log.epoch_number = tonumber(v.emited_topics[2]) + log.input_lower_bound = tonumber(v.decoded_data[1]) + log.input_upper_bound = tonumber(v.decoded_data[2]) + log.initial_machine_state_hash = v.decoded_data[3] + log.tournament = v.decoded_data[5] ret[k] = log end From 270d2618c51e60104e57987ea0ea352a9500da91 Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Mon, 20 Jul 2026 08:37:34 -0300 Subject: [PATCH 030/113] feat(node)!: rewrite the rollups node on one engine The rollups node becomes a single crate: worker modules synchronize through the storage module (the only SQL surface), and one spec-oracled engine (src/engine: structure, stf, ruler, and the dispute facade) serves both the runner's forward schedule and the dispute path's random access. - One leaf semantics: process_input and the second geometry implementation are gone. The runner schedules the engine's collect() per input window; the window-root quartet row is the only durable level-0 artifact; settlement and dispute serving read the same rows (frontier fold, strict and prefix-bounded). - The boundary store (stage+rename, write-once cells, clone-based revert) carries both regimes' revert insurance; corruption tripwires panic through the loud process exit, never retry. - Revert on RX_REJECTED only, matching AdvanceStatus.sol - the chain never reverts an exception. - The staged settlement protocol: the epoch manager drives sentry claim, stage, and accept per tick, each step guarded and idempotent; a sentry always claims the locally computed post-epoch hash, never the staged value. settlement_info carries final_state, captured from the boundary store at roll (the new epoch's initial boundary IS the post-epoch state). - Absorbs prt/client-rs (the tournament reader/sender/gc and the hero's react loop, traced against the contracts - the audit ledger records verdicts and clean checks) and common-rs (merkle, arithmetic, and kms become node modules; the node was their only consumer). - Chain recordings - raw devnet log ranges captured after e2e disputes settle - are committed fixtures for the tournament fold, decoded through the production bindings. Replaces the epoch-manager/state-manager/blockchain-reader/ prt-node crates, prt/client-rs, and common-rs. --- .cargo/config.toml | 6 - Cargo.lock | 150 +- Cargo.toml | 25 +- cartesi-rollups/node/Cargo.toml | 63 + cartesi-rollups/node/README.md | 33 + .../node/blockchain-reader/Cargo.toml | 42 - .../node/blockchain-reader/src/error.rs | 37 - .../node/cartesi-rollups-prt-node/Cargo.toml | 36 - .../node/cartesi-rollups-prt-node/src/lib.rs | 135 - .../node/cartesi-rollups-prt-node/src/main.rs | 41 - cartesi-rollups/node/epoch-manager/Cargo.toml | 22 - cartesi-rollups/node/epoch-manager/src/lib.rs | 375 - .../node/machine-runner/Cargo.toml | 24 - .../node/machine-runner/src/lib.rs | 93 - .../src/args.rs | 49 +- .../node/src/arithmetic.rs | 2 +- cartesi-rollups/node/src/bin/measure.rs | 988 ++ cartesi-rollups/node/src/bin/record_chain.rs | 90 + .../lib.rs => src/blockchain_reader/mod.rs} | 418 +- .../blockchain_reader}/test_utils.rs | 44 +- cartesi-rollups/node/src/chain.rs | 157 + cartesi-rollups/node/src/engine/cache.rs | 108 + cartesi-rollups/node/src/engine/config.rs | 138 + .../node/src/engine}/constants.rs | 83 +- cartesi-rollups/node/src/engine/dispute.rs | 433 + .../node/src/engine/machine_stf.rs | 679 + cartesi-rollups/node/src/engine/mod.rs | 52 + cartesi-rollups/node/src/engine/ruler.rs | 495 + cartesi-rollups/node/src/engine/spec.rs | 907 ++ cartesi-rollups/node/src/engine/stf.rs | 360 + cartesi-rollups/node/src/engine/structure.rs | 353 + .../src => src/epoch_manager}/error.rs | 4 +- cartesi-rollups/node/src/epoch_manager/mod.rs | 346 + .../node/src/hero}/error.rs | 11 +- cartesi-rollups/node/src/hero/gc.rs | 84 + cartesi-rollups/node/src/hero/mod.rs | 1269 ++ .../lib.rs => cartesi-rollups/node/src/kms.rs | 2 +- cartesi-rollups/node/src/lib.rs | 179 + .../src => src/machine_runner}/error.rs | 14 +- .../node/src/machine_runner/mod.rs | 141 + cartesi-rollups/node/src/main.rs | 19 + .../node/src/merkle}/digest/keccak.rs | 0 .../node/src/merkle}/digest/mod.rs | 0 .../node/src/merkle/mod.rs | 2 +- .../node/src/merkle}/tree.rs | 12 +- .../node/src/merkle}/tree_builder.rs | 16 +- .../src/provider.rs | 4 +- cartesi-rollups/node/src/storage/advance.rs | 775 + cartesi-rollups/node/src/storage/convert.rs | 33 + cartesi-rollups/node/src/storage/dispute.rs | 307 + cartesi-rollups/node/src/storage/error.rs | 36 + cartesi-rollups/node/src/storage/ingest.rs | 265 + cartesi-rollups/node/src/storage/mod.rs | 364 + cartesi-rollups/node/src/storage/open.rs | 310 + cartesi-rollups/node/src/storage/queries.rs | 231 + .../node/src/storage/rollups_machine.rs | 169 + cartesi-rollups/node/src/storage/snapshots.rs | 874 + .../node/src/storage/sql/discipline.rs | 381 + .../node/src/storage/sql/migrations.rs | 50 + .../node/src/storage/sql/migrations.sql | 354 + cartesi-rollups/node/src/storage/sql/mod.rs | 13 + .../src => src/storage}/sql/test_helper.rs | 21 +- cartesi-rollups/node/src/sync.rs | 140 + cartesi-rollups/node/src/tournament/fold.rs | 579 + .../node}/src/tournament/mod.rs | 9 +- cartesi-rollups/node/src/tournament/reader.rs | 598 + .../node}/src/tournament/sender.rs | 13 +- cartesi-rollups/node/src/tournament/types.rs | 302 + cartesi-rollups/node/state-manager/Cargo.toml | 28 - cartesi-rollups/node/state-manager/src/lib.rs | 128 - .../src/persistent_state_access.rs | 547 - .../node/state-manager/src/rollups_machine.rs | 212 - .../state-manager/src/sql/consensus_data.rs | 685 - .../node/state-manager/src/sql/migrations.rs | 12 - .../node/state-manager/src/sql/migrations.sql | 73 - .../node/state-manager/src/sql/mod.rs | 151 - .../node/state-manager/src/sql/rollup_data.rs | 531 - .../node/state-manager/src/state_manager.rs | 101 - .../node/state-manager/src/sync.rs | 144 - .../node/tests/common/epoch_data.rs | 33 + .../node/tests/common}/instance.rs | 92 +- .../node/tests/common/machine_error.rs | 7 - cartesi-rollups/node/tests/common/mod.rs | 13 + .../node/tests/common/prototype.rs | 196 +- cartesi-rollups/node/tests/engine_machine.rs | 476 + .../tests/fixtures/chain-recordings/README.md | 31 + .../chain-recordings/echo_simple.json | 3572 ++++ .../chain-recordings/multi_sybil.json | 6784 ++++++++ .../chain-recordings/multilevel_stf.json | 13406 ++++++++++++++++ .../node/tests/fixtures/engine_echo.json | 6 + cartesi-rollups/node/tests/tournament_fold.rs | 319 + common-rs/.gitignore | 1 - common-rs/README.md | 1 - common-rs/arithmetic/Cargo.toml | 11 - common-rs/kms/.dockerignore | 1 - common-rs/kms/.gitignore | 1 - common-rs/kms/Cargo.toml | 29 - common-rs/kms/README.md | 28 - common-rs/kms/aws.sh | 22 - common-rs/kms/compose.yaml | 31 - common-rs/merkle/Cargo.toml | 19 - .../cartesi-machine-sys/build.rs | 88 +- .../cartesi-machine/src/config/machine.rs | 8 +- .../cartesi-machine/src/constants.rs | 8 +- .../cartesi-machine/src/machine.rs | 180 +- .../cartesi-machine/src/types/mod.rs | 27 + prt/client-rs/core/Cargo.toml | 50 - .../core/src/db/dispute_state_access.rs | 411 - prt/client-rs/core/src/db/mod.rs | 6 - prt/client-rs/core/src/db/sql/dispute_data.rs | 305 - prt/client-rs/core/src/db/sql/error.rs | 36 - prt/client-rs/core/src/db/sql/migrations.rs | 12 - prt/client-rs/core/src/db/sql/migrations.sql | 13 - prt/client-rs/core/src/db/sql/mod.rs | 3 - prt/client-rs/core/src/lib.rs | 7 - .../core/src/machine/commitment_builder.rs | 57 - prt/client-rs/core/src/machine/mod.rs | 16 - prt/client-rs/core/src/strategy/gc.rs | 102 - prt/client-rs/core/src/strategy/mod.rs | 6 - prt/client-rs/core/src/strategy/player.rs | 490 - prt/client-rs/core/src/tournament/config.rs | 149 - prt/client-rs/core/src/tournament/reader.rs | 455 - .../core/src/tournament/tournament.rs | 153 - 123 files changed, 38151 insertions(+), 6487 deletions(-) delete mode 100644 .cargo/config.toml create mode 100644 cartesi-rollups/node/Cargo.toml delete mode 100644 cartesi-rollups/node/blockchain-reader/Cargo.toml delete mode 100644 cartesi-rollups/node/blockchain-reader/src/error.rs delete mode 100644 cartesi-rollups/node/cartesi-rollups-prt-node/Cargo.toml delete mode 100644 cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs delete mode 100644 cartesi-rollups/node/cartesi-rollups-prt-node/src/main.rs delete mode 100644 cartesi-rollups/node/epoch-manager/Cargo.toml delete mode 100644 cartesi-rollups/node/epoch-manager/src/lib.rs delete mode 100644 cartesi-rollups/node/machine-runner/Cargo.toml delete mode 100644 cartesi-rollups/node/machine-runner/src/lib.rs rename cartesi-rollups/node/{cartesi-rollups-prt-node => }/src/args.rs (80%) rename common-rs/arithmetic/src/lib.rs => cartesi-rollups/node/src/arithmetic.rs (81%) create mode 100644 cartesi-rollups/node/src/bin/measure.rs create mode 100644 cartesi-rollups/node/src/bin/record_chain.rs rename cartesi-rollups/node/{blockchain-reader/src/lib.rs => src/blockchain_reader/mod.rs} (60%) rename cartesi-rollups/node/{blockchain-reader/src => src/blockchain_reader}/test_utils.rs (73%) create mode 100644 cartesi-rollups/node/src/chain.rs create mode 100644 cartesi-rollups/node/src/engine/cache.rs create mode 100644 cartesi-rollups/node/src/engine/config.rs rename {prt/client-rs/core/src/machine => cartesi-rollups/node/src/engine}/constants.rs (51%) create mode 100644 cartesi-rollups/node/src/engine/dispute.rs create mode 100644 cartesi-rollups/node/src/engine/machine_stf.rs create mode 100644 cartesi-rollups/node/src/engine/mod.rs create mode 100644 cartesi-rollups/node/src/engine/ruler.rs create mode 100644 cartesi-rollups/node/src/engine/spec.rs create mode 100644 cartesi-rollups/node/src/engine/stf.rs create mode 100644 cartesi-rollups/node/src/engine/structure.rs rename cartesi-rollups/node/{epoch-manager/src => src/epoch_manager}/error.rs (84%) create mode 100644 cartesi-rollups/node/src/epoch_manager/mod.rs rename {prt/client-rs/core/src/strategy => cartesi-rollups/node/src/hero}/error.rs (67%) create mode 100644 cartesi-rollups/node/src/hero/gc.rs create mode 100644 cartesi-rollups/node/src/hero/mod.rs rename common-rs/kms/src/lib.rs => cartesi-rollups/node/src/kms.rs (99%) create mode 100644 cartesi-rollups/node/src/lib.rs rename cartesi-rollups/node/{machine-runner/src => src/machine_runner}/error.rs (70%) create mode 100644 cartesi-rollups/node/src/machine_runner/mod.rs create mode 100644 cartesi-rollups/node/src/main.rs rename {common-rs/merkle/src => cartesi-rollups/node/src/merkle}/digest/keccak.rs (100%) rename {common-rs/merkle/src => cartesi-rollups/node/src/merkle}/digest/mod.rs (100%) rename common-rs/merkle/src/lib.rs => cartesi-rollups/node/src/merkle/mod.rs (89%) rename {common-rs/merkle/src => cartesi-rollups/node/src/merkle}/tree.rs (96%) rename {common-rs/merkle/src => cartesi-rollups/node/src/merkle}/tree_builder.rs (94%) rename cartesi-rollups/node/{cartesi-rollups-prt-node => }/src/provider.rs (96%) create mode 100644 cartesi-rollups/node/src/storage/advance.rs create mode 100644 cartesi-rollups/node/src/storage/convert.rs create mode 100644 cartesi-rollups/node/src/storage/dispute.rs create mode 100644 cartesi-rollups/node/src/storage/error.rs create mode 100644 cartesi-rollups/node/src/storage/ingest.rs create mode 100644 cartesi-rollups/node/src/storage/mod.rs create mode 100644 cartesi-rollups/node/src/storage/open.rs create mode 100644 cartesi-rollups/node/src/storage/queries.rs create mode 100644 cartesi-rollups/node/src/storage/rollups_machine.rs create mode 100644 cartesi-rollups/node/src/storage/snapshots.rs create mode 100644 cartesi-rollups/node/src/storage/sql/discipline.rs create mode 100644 cartesi-rollups/node/src/storage/sql/migrations.rs create mode 100644 cartesi-rollups/node/src/storage/sql/migrations.sql create mode 100644 cartesi-rollups/node/src/storage/sql/mod.rs rename cartesi-rollups/node/{state-manager/src => src/storage}/sql/test_helper.rs (60%) create mode 100644 cartesi-rollups/node/src/sync.rs create mode 100644 cartesi-rollups/node/src/tournament/fold.rs rename {prt/client-rs/core => cartesi-rollups/node}/src/tournament/mod.rs (80%) create mode 100644 cartesi-rollups/node/src/tournament/reader.rs rename {prt/client-rs/core => cartesi-rollups/node}/src/tournament/sender.rs (97%) create mode 100644 cartesi-rollups/node/src/tournament/types.rs delete mode 100644 cartesi-rollups/node/state-manager/Cargo.toml delete mode 100644 cartesi-rollups/node/state-manager/src/lib.rs delete mode 100644 cartesi-rollups/node/state-manager/src/persistent_state_access.rs delete mode 100644 cartesi-rollups/node/state-manager/src/rollups_machine.rs delete mode 100644 cartesi-rollups/node/state-manager/src/sql/consensus_data.rs delete mode 100644 cartesi-rollups/node/state-manager/src/sql/migrations.rs delete mode 100644 cartesi-rollups/node/state-manager/src/sql/migrations.sql delete mode 100644 cartesi-rollups/node/state-manager/src/sql/mod.rs delete mode 100644 cartesi-rollups/node/state-manager/src/sql/rollup_data.rs delete mode 100644 cartesi-rollups/node/state-manager/src/state_manager.rs delete mode 100644 cartesi-rollups/node/state-manager/src/sync.rs create mode 100644 cartesi-rollups/node/tests/common/epoch_data.rs rename {prt/client-rs/core/src/machine => cartesi-rollups/node/tests/common}/instance.rs (85%) rename prt/client-rs/core/src/machine/error.rs => cartesi-rollups/node/tests/common/machine_error.rs (76%) create mode 100644 cartesi-rollups/node/tests/common/mod.rs rename prt/client-rs/core/src/machine/commitment.rs => cartesi-rollups/node/tests/common/prototype.rs (50%) create mode 100644 cartesi-rollups/node/tests/engine_machine.rs create mode 100644 cartesi-rollups/node/tests/fixtures/chain-recordings/README.md create mode 100644 cartesi-rollups/node/tests/fixtures/chain-recordings/echo_simple.json create mode 100644 cartesi-rollups/node/tests/fixtures/chain-recordings/multi_sybil.json create mode 100644 cartesi-rollups/node/tests/fixtures/chain-recordings/multilevel_stf.json create mode 100644 cartesi-rollups/node/tests/fixtures/engine_echo.json create mode 100644 cartesi-rollups/node/tests/tournament_fold.rs delete mode 100644 common-rs/.gitignore delete mode 100644 common-rs/README.md delete mode 100644 common-rs/arithmetic/Cargo.toml delete mode 100644 common-rs/kms/.dockerignore delete mode 100644 common-rs/kms/.gitignore delete mode 100644 common-rs/kms/Cargo.toml delete mode 100644 common-rs/kms/README.md delete mode 100755 common-rs/kms/aws.sh delete mode 100644 common-rs/kms/compose.yaml delete mode 100644 common-rs/merkle/Cargo.toml delete mode 100644 prt/client-rs/core/Cargo.toml delete mode 100644 prt/client-rs/core/src/db/dispute_state_access.rs delete mode 100644 prt/client-rs/core/src/db/mod.rs delete mode 100644 prt/client-rs/core/src/db/sql/dispute_data.rs delete mode 100644 prt/client-rs/core/src/db/sql/error.rs delete mode 100644 prt/client-rs/core/src/db/sql/migrations.rs delete mode 100644 prt/client-rs/core/src/db/sql/migrations.sql delete mode 100644 prt/client-rs/core/src/db/sql/mod.rs delete mode 100644 prt/client-rs/core/src/lib.rs delete mode 100644 prt/client-rs/core/src/machine/commitment_builder.rs delete mode 100644 prt/client-rs/core/src/machine/mod.rs delete mode 100644 prt/client-rs/core/src/strategy/gc.rs delete mode 100644 prt/client-rs/core/src/strategy/mod.rs delete mode 100644 prt/client-rs/core/src/strategy/player.rs delete mode 100644 prt/client-rs/core/src/tournament/config.rs delete mode 100644 prt/client-rs/core/src/tournament/reader.rs delete mode 100644 prt/client-rs/core/src/tournament/tournament.rs diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index c37e44b07..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,6 +0,0 @@ -[target.aarch64-apple-darwin] -# Pass the correct C++ library paths to the linker -rustflags = [ - "-C", "link-arg=-L/opt/homebrew/opt/llvm/lib/c++", - "-C", "link-arg=-Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++" -] diff --git a/Cargo.lock b/Cargo.lock index dbb0d57e4..fe7e1bffd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1662,10 +1662,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cartesi-dave-arithmetic" -version = "2.0.0" - [[package]] name = "cartesi-dave-contracts" version = "0.1.0" @@ -1673,30 +1669,6 @@ dependencies = [ "alloy", ] -[[package]] -name = "cartesi-dave-kms" -version = "2.0.0" -dependencies = [ - "alloy", - "anyhow", - "aws-config", - "aws-sdk-kms", - "lazy_static", - "testcontainers-modules", - "tokio", -] - -[[package]] -name = "cartesi-dave-merkle" -version = "2.0.0" -dependencies = [ - "alloy", - "hex", - "ruint", - "thiserror", - "tiny-keccak", -] - [[package]] name = "cartesi-machine" version = "2.0.0" @@ -1731,34 +1703,6 @@ dependencies = [ "alloy", ] -[[package]] -name = "cartesi-prt-core" -version = "2.0.0" -dependencies = [ - "alloy", - "anyhow", - "async-recursion", - "async-trait", - "cartesi-dave-arithmetic", - "cartesi-dave-kms", - "cartesi-dave-merkle", - "cartesi-machine", - "cartesi-prt-contracts", - "clap", - "hex", - "lazy_static", - "log", - "num-traits", - "ruint", - "rusqlite", - "rusqlite_migration", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", -] - [[package]] name = "cartesi-rollups-contracts" version = "3.0.0-alpha.4" @@ -1777,20 +1721,30 @@ dependencies = [ "alloy-chains", "alloy-transport", "anyhow", - "cartesi-dave-kms", + "async-recursion", + "async-trait", + "aws-config", + "aws-sdk-kms", + "cartesi-dave-contracts", "cartesi-machine", - "cartesi-prt-core", + "cartesi-prt-contracts", "cartesi-rollups-contracts", "clap", "env_logger", "futures", + "hex", + "lazy_static", "log", "reqwest", - "rollups-blockchain-reader", - "rollups-epoch-manager", - "rollups-machine-runner", - "rollups-state-manager", + "ruint", "rusqlite", + "rusqlite_migration", + "serde", + "serde_json", + "tempfile", + "testcontainers-modules", + "thiserror", + "tiny-keccak", "tokio", ] @@ -4377,78 +4331,6 @@ dependencies = [ "rustc-hex", ] -[[package]] -name = "rollups-blockchain-reader" -version = "2.0.0" -dependencies = [ - "alloy", - "anyhow", - "async-recursion", - "cartesi-dave-contracts", - "cartesi-dave-merkle", - "cartesi-machine", - "cartesi-prt-contracts", - "cartesi-rollups-contracts", - "clap", - "log", - "num-traits", - "rollups-state-manager", - "rusqlite", - "rusqlite_migration", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", -] - -[[package]] -name = "rollups-epoch-manager" -version = "2.0.0" -dependencies = [ - "alloy", - "anyhow", - "cartesi-dave-contracts", - "cartesi-prt-core", - "log", - "num-traits", - "rollups-state-manager", - "thiserror", - "tokio", -] - -[[package]] -name = "rollups-machine-runner" -version = "2.0.0" -dependencies = [ - "alloy", - "cartesi-dave-merkle", - "cartesi-machine", - "cartesi-prt-core", - "cartesi-rollups-contracts", - "hex", - "log", - "rollups-state-manager", - "thiserror", -] - -[[package]] -name = "rollups-state-manager" -version = "2.0.0" -dependencies = [ - "alloy", - "anyhow", - "cartesi-dave-merkle", - "cartesi-machine", - "cartesi-prt-core", - "hex", - "lazy_static", - "rusqlite", - "rusqlite_migration", - "tempfile", - "thiserror", -] - [[package]] name = "ruint" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 4b16169f3..93d524c76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,20 +5,8 @@ members = [ "cartesi-rollups/contracts/bindings-rs", "prt/contracts/bindings-rs", - # common-rs - "common-rs/merkle", - "common-rs/arithmetic", - "common-rs/kms", - - # prt - "prt/client-rs/core", - # rollups-node - "cartesi-rollups/node/blockchain-reader", - "cartesi-rollups/node/cartesi-rollups-prt-node", - "cartesi-rollups/node/epoch-manager", - "cartesi-rollups/node/machine-runner", - "cartesi-rollups/node/state-manager", + "cartesi-rollups/node", # machine bindings "machine/rust-bindings/cartesi-machine", @@ -53,18 +41,8 @@ cartesi-dave-contracts = { path = "cartesi-rollups/contracts/bindings-rs" } cartesi-prt-contracts = { path = "prt/contracts/bindings-rs" } # rollups-node -rollups-blockchain-reader = { version = "2.0.0", path = "cartesi-rollups/node/blockchain-reader" } -rollups-epoch-manager = { version = "2.0.0", path = "cartesi-rollups/node/epoch-manager" } -rollups-machine-runner = { version = "2.0.0", path = "cartesi-rollups/node/machine-runner" } -rollups-state-manager = { version = "2.0.0", path = "cartesi-rollups/node/state-manager" } -# common-rs -cartesi-dave-arithmetic = { path = "common-rs/arithmetic" } -cartesi-dave-merkle = { path = "common-rs/merkle" } -cartesi-dave-kms = { path = "common-rs/kms" } -# prt -cartesi-prt-core = { path = "prt/client-rs/core" } ## Dependencies @@ -106,4 +84,3 @@ rusqlite_migration = "1.2.0" clap = { version = "4.5", features = ["derive", "env"] } hex = "0.4" log = "0.4" -num-traits = "0.2" diff --git a/cartesi-rollups/node/Cargo.toml b/cartesi-rollups/node/Cargo.toml new file mode 100644 index 000000000..9986b4b13 --- /dev/null +++ b/cartesi-rollups/node/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "cartesi-rollups-prt-node" +version = { workspace = true } + +authors = { workspace = true } +description = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license-file = { workspace = true } +readme = { workspace = true } +repository = { workspace = true } + +[dependencies] +cartesi-machine = { workspace = true } +cartesi-dave-contracts = { workspace = true } + +cartesi-rollups-contracts = { workspace = true } +cartesi-prt-contracts = { workspace = true } + +# the folded-in kms signer (formerly common-rs/kms) +aws-config = { version = "1.6", default-features = false, features = [ + "rustls", + "rt-tokio", +] } +aws-sdk-kms = { version = "1.65", default-features = false, features = [ + "rustls", + "rt-tokio", +] } + +# the folded-in merkle module (formerly common-rs/merkle) +ruint = { workspace = true } +tiny-keccak = { workspace = true } + +alloy = { workspace = true, features = ["signer-aws"] } +alloy-transport = { workspace = true } +alloy-chains = { workspace = true } +reqwest = { workspace = true } + +anyhow = { workspace = true } +async-recursion = { workspace = true } +async-trait = { workspace = true } +clap = { workspace = true } +futures = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +lazy_static = { workspace = true } +log = { workspace = true } +rusqlite = { workspace = true } +rusqlite_migration = { workspace = true } +hex = { workspace = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tempfile = "3" +env_logger = "0.11.5" + +[dev-dependencies] +alloy = { workspace = true, features = ["node-bindings", "rpc-types"] } +hex = "0.4.3" +tempfile = "3" +# the kms module's localstack round-trip test +testcontainers-modules = { version = "0.13.0", default-features = false, features = [ + "localstack", +] } diff --git a/cartesi-rollups/node/README.md b/cartesi-rollups/node/README.md index 3f1a1b32d..90bbabeea 100644 --- a/cartesi-rollups/node/README.md +++ b/cartesi-rollups/node/README.md @@ -1,5 +1,38 @@ # Dave Rollups Node +The prototype PRT validator node, one crate: it follows an application's +inputs, recomputes its state, and defends the correct result in disputes. +Architecture and known debts: [docs/node-architecture.md](../../docs/node-architecture.md). +How epochs and disputes flow: [docs/epoch-lifecycle.md](../../docs/epoch-lifecycle.md). + +## Layout (`src/`) + +Worker modules - one thread each, synchronizing through the node +database: `blockchain_reader` (chain logs to db), `machine_runner` +(inputs to machine execution to leaf hashes and snapshots), +`epoch_manager` (settlement and disputes). `storage` is the single +view of that database and the only module that speaks SQL. + +The dispute engine (formerly the `cartesi-prt-core` crate): + +- `machine/` - commitment construction: driving the Cartesi Machine + through meta-cycles, building leaf sequences, generating on-chain step + proofs (`get_logs`). Read `docs/computation-hash.md` first; this is the + arcane part. +- `sling/` - the quartet cache, the ruler geometry engine, and the + dispute source serving every tree query a dispute needs; the design + lives in `docs/plans/sling-design.md`. +- `strategy/` - the `Player`: the react loop that joins tournaments, + bisects matches, seals, proves, and wins timeouts. Plus the garbage + collector that frees bonds. +- `tournament/` - chain interface: `StateReader` (reconstructs the full + tournament tree from events and calls) and `ArenaSender` (transaction + wrappers, revert-tolerant). + +The Lua client (`prt/client-lua/`) implements the same commitment +construction and honest strategy; the e2e tests assert both agree. Keep +it that way. + ## Build (release) Run at the repository root: diff --git a/cartesi-rollups/node/blockchain-reader/Cargo.toml b/cartesi-rollups/node/blockchain-reader/Cargo.toml deleted file mode 100644 index b7d70bd50..000000000 --- a/cartesi-rollups/node/blockchain-reader/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "rollups-blockchain-reader" -version = { workspace = true } - -authors = { workspace = true } -description = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -readme = { workspace = true } -repository = { workspace = true } - -[dependencies] -rollups-state-manager = { workspace = true } -cartesi-dave-contracts = { workspace = true } -cartesi-rollups-contracts = { workspace = true } -cartesi-dave-merkle = { workspace = true } - -cartesi-machine = { workspace = true } - -alloy = { workspace = true } -async-recursion = { workspace = true } -clap = { workspace = true } -log = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -num-traits = { workspace = true } - -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -[dev-dependencies] -alloy = { workspace = true, features = ["node-bindings", "rpc-types"] } -cartesi-prt-contracts = { workspace = true } - -rusqlite = { workspace = true } -rusqlite_migration = { workspace = true } - -tempfile = "3" - -anyhow = { workspace = true } -clap = { workspace = true } diff --git a/cartesi-rollups/node/blockchain-reader/src/error.rs b/cartesi-rollups/node/blockchain-reader/src/error.rs deleted file mode 100644 index 3591ef964..000000000 --- a/cartesi-rollups/node/blockchain-reader/src/error.rs +++ /dev/null @@ -1,37 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use alloy::{contract::Error as ContractError, transports::http::reqwest::Url}; -use std::str::FromStr; -use thiserror::Error; - -use rollups_state_manager::StateAccessError; - -#[derive(Error, Debug)] -pub struct ProviderErrors(pub Vec); - -impl std::fmt::Display for ProviderErrors { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Provider error: {:?}", self.0) - } -} - -#[derive(Error, Debug)] -pub enum BlockchainReaderError { - #[error(transparent)] - Providers { - #[from] - source: ProviderErrors, - }, - - #[error("Parse error: {0}")] - ParseError(::Err), - - #[error(transparent)] - StateManagerError { - #[from] - source: StateAccessError, - }, -} - -pub type Result = std::result::Result; diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/Cargo.toml b/cartesi-rollups/node/cartesi-rollups-prt-node/Cargo.toml deleted file mode 100644 index 04e082834..000000000 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "cartesi-rollups-prt-node" -version = { workspace = true } - -authors = { workspace = true } -description = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -readme = { workspace = true } -repository = { workspace = true } - -[dependencies] -rollups-blockchain-reader = { workspace = true } -rollups-epoch-manager = { workspace = true } -rollups-machine-runner = { workspace = true } -rollups-state-manager = { workspace = true } - -cartesi-machine = { workspace = true } -cartesi-dave-kms = { workspace = true } - -cartesi-rollups-contracts = { workspace = true } -cartesi-prt-core = { workspace = true } - -alloy = { workspace = true } -alloy-transport = { workspace = true } -alloy-chains = { workspace = true } -reqwest = { workspace = true } - -anyhow = { workspace = true } -clap = { workspace = true } -futures = { workspace = true } -tokio = { workspace = true } -log = { workspace = true } -rusqlite = { workspace = true } -env_logger = "0.11.5" diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs b/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs deleted file mode 100644 index 25eed47f0..000000000 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/lib.rs +++ /dev/null @@ -1,135 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pub mod args; -pub mod provider; - -use args::PRTConfig; - -use log::{error, info}; -use std::{sync::Arc, thread}; -use tokio::sync::Mutex; - -use cartesi_prt_core::tournament::EthArenaSender; -use rollups_blockchain_reader::BlockchainReader; -use rollups_epoch_manager::EpochManager; -use rollups_machine_runner::MachineRunner; -use rollups_state_manager::sync::Watch; - -macro_rules! notify_all { - ($worker:literal, $watch:expr, $res:expr) => {{ - match $res { - Ok(Ok(())) => { - info!("{} shutdown gracefully", $worker); - } - Ok(Err(e)) => { - error!("{} returned error: {e}", $worker); - info!("Starting shutdown"); - $watch.notify(Arc::new(anyhow::anyhow!(e))); - } - Err(e) => { - error!("{} panicked: {e:?}", $worker); - info!("Starting shutdown"); - $watch.notify(Arc::new(anyhow::anyhow!(format!("{e:?}")))); - } - } - }}; -} - -fn create_runtime(service: &str) -> tokio::runtime::Runtime { - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap_or_else(|e| panic!("`{}` runtime build failure: {e}", service)) -} - -pub fn create_blockchain_reader_task( - watch: Watch, - parameters: &PRTConfig, -) -> thread::JoinHandle<()> { - let params = parameters.clone(); - let inner_watch = watch.clone(); - - thread::Builder::new() - .name("blockchain-reader".into()) - .spawn(move || { - let res = std::panic::catch_unwind(|| { - let rt = create_runtime("BlockchainReader"); - - rt.block_on(async move { - let state_manager = params.state_access().unwrap(); - let blockchain_reader = BlockchainReader::new( - state_manager, - params.address_book, - params.sleep_duration, - params.long_block_range_error_codes.clone(), - ); - - blockchain_reader - .execution_loop(inner_watch, params.provider().await) - .await - }) - .inspect_err(|e| error!("{e}")) - }); - - notify_all!("Blockchain reader", watch, res); - }) - .expect("failed to spawn blockchain reader thread") -} - -pub fn create_epoch_manager_task(watch: Watch, parameters: &PRTConfig) -> thread::JoinHandle<()> { - let params = parameters.clone(); - let inner_watch = watch.clone(); - - thread::Builder::new() - .name("epoch-manager".into()) - .spawn(move || { - let res = std::panic::catch_unwind(|| { - let rt = create_runtime("EpochManager"); - rt.block_on(async move { - let state_manager = params.state_access().unwrap(); - let provider = params.provider().await; - let arena_sender = EthArenaSender::new(provider.clone()) - .expect("could not create arena sender"); - - let epoch_manager = EpochManager::new( - Arc::new(Mutex::new(arena_sender)), - params.address_book.consensus, - params.signer_address, - state_manager, - params.sleep_duration, - params.long_block_range_error_codes.clone(), - ); - - epoch_manager.execution_loop(inner_watch, provider).await - }) - .inspect_err(|e| error!("{e}")) - }); - - notify_all!("Epoch manager", watch, res); - }) - .expect("failed to spawn epoch manager thread") -} - -pub fn create_machine_runner_task(watch: Watch, parameters: &PRTConfig) -> thread::JoinHandle<()> { - let params = parameters.clone(); - - thread::Builder::new() - .name("machine-runner".into()) - .spawn(move || { - let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let state_manager = params.state_access().unwrap(); - - let mut machine_runner = MachineRunner::new(state_manager, params.sleep_duration) - .inspect_err(|e| error!("{e}")) - .unwrap(); - - machine_runner - .start(watch.clone()) - .inspect_err(|e| error!("{e}")) - })); - - notify_all!("Machine runner", watch, res); - }) - .expect("failed to spawn machine runner thread") -} diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/main.rs b/cartesi-rollups/node/cartesi-rollups-prt-node/src/main.rs deleted file mode 100644 index 66bf3a6fb..000000000 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use cartesi_rollups_prt_node::{ - args::PRTConfig, create_blockchain_reader_task, create_epoch_manager_task, - create_machine_runner_task, -}; -use rollups_state_manager::sync::Watch; - -use anyhow::Result; -use env_logger::Env; -use log::info; - -fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - info!("Hello from PRT Rollup Node!"); - - let (config, _state_manager) = PRTConfig::setup(); - info!("Running with config:\n{}", config); - - // spawn workers - let watch = Watch::default(); - let blockchain_reader_task = create_blockchain_reader_task(watch.clone(), &config); - let epoch_manager_task = create_epoch_manager_task(watch.clone(), &config); - let machine_runner_task = create_machine_runner_task(watch.clone(), &config); - - // monitor status - let err = loop { - match watch.wait(std::time::Duration::from_millis(1000)) { - std::ops::ControlFlow::Continue(()) => continue, - std::ops::ControlFlow::Break(e) => break e, - } - }; - - // shutdown - let _ = blockchain_reader_task.join(); - let _ = epoch_manager_task.join(); - let _ = machine_runner_task.join(); - - anyhow::bail!(err); -} diff --git a/cartesi-rollups/node/epoch-manager/Cargo.toml b/cartesi-rollups/node/epoch-manager/Cargo.toml deleted file mode 100644 index 22591a1ed..000000000 --- a/cartesi-rollups/node/epoch-manager/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "rollups-epoch-manager" -version.workspace = true -authors.workspace = true -description.workspace = true -edition.workspace = true -homepage.workspace = true -license-file.workspace = true -readme.workspace = true -repository.workspace = true - -[dependencies] -cartesi-dave-contracts = { workspace = true } -cartesi-prt-core = { workspace = true } -rollups-state-manager = { workspace = true } - -anyhow = { workspace = true } -alloy = { workspace = true } -log = { workspace = true } -num-traits = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } diff --git a/cartesi-rollups/node/epoch-manager/src/lib.rs b/cartesi-rollups/node/epoch-manager/src/lib.rs deleted file mode 100644 index 78c219fdf..000000000 --- a/cartesi-rollups/node/epoch-manager/src/lib.rs +++ /dev/null @@ -1,375 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -mod error; - -use alloy::{ - primitives::{Address, B256}, - providers::{DynProvider, Provider}, -}; -use error::Result; -use log::{debug, info, trace}; -use num_traits::cast::ToPrimitive; -use std::{ops::ControlFlow, sync::Arc, time::Duration}; -use tokio::sync::Mutex; - -use cartesi_dave_contracts::dave_consensus::DaveConsensus; -use cartesi_prt_core::{ - db::dispute_state_access::{Input, Leaf}, - strategy::player::Player, - tournament::{ArenaSender, allow_revert_rethrow_others}, -}; -use rollups_state_manager::{Epoch, Proof, StateManager, sync::Watch}; - -pub struct EpochManager { - arena_sender: Arc>, - consensus: Address, - signer_address: Address, - sleep_duration: Duration, - long_block_range_error_codes: Vec, - state_manager: SM, - last_react_epoch: (Option>, u64), -} - -impl EpochManager { - pub fn new( - arena_sender: Arc>, - consensus_address: Address, - signer_address: Address, - state_manager: SM, - sleep_duration: Duration, - long_block_range_error_codes: Vec, - ) -> Self { - Self { - arena_sender, - consensus: consensus_address, - signer_address, - sleep_duration, - long_block_range_error_codes, - state_manager, - last_react_epoch: (None, 0), - } - } - - pub async fn execution_loop(mut self, watch: Watch, provider: DynProvider) -> Result<()> { - let dave_consensus = DaveConsensus::new(self.consensus, provider.clone()); - - loop { - self.try_settle_epoch(&dave_consensus).await?; - self.try_react_epoch(provider.clone()).await?; - - if matches!(watch.wait(self.sleep_duration), ControlFlow::Break(_)) { - break Ok(()); - } - } - } - - pub async fn try_settle_epoch( - &mut self, - dave_consensus: &DaveConsensus::DaveConsensusInstance< - DynProvider, - alloy::network::Ethereum, - >, - ) -> Result<()> { - self.try_submit_sentry_claim(dave_consensus).await?; - self.try_stage_tournament_result(dave_consensus).await?; - self.try_accept_tournament_result(dave_consensus).await?; - Ok(()) - } - - async fn try_submit_sentry_claim( - &mut self, - dave_consensus: &DaveConsensus::DaveConsensusInstance< - DynProvider, - alloy::network::Ethereum, - >, - ) -> Result<()> { - let sentry_id = dave_consensus - .getSentryId(self.signer_address) - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - if sentry_id == 0 { - trace!( - "signer {} is not a sentry of DaveConsensus@{}", - self.signer_address, - dave_consensus.address() - ); - return Ok(()); - } - - let current_sealed_epoch = dave_consensus - .getCurrentSealedEpoch() - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - let epoch_number = current_sealed_epoch.epochNumber; - - let has_voted = dave_consensus - .hasSentryClaimedInEpoch(epoch_number, sentry_id) - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - if has_voted { - trace!( - "sentry {} (id {}) has already voted for epoch {} of DaveConsensus@{}", - self.signer_address, - sentry_id, - epoch_number, - dave_consensus.address() - ); - return Ok(()); - } - - let can_accept = dave_consensus - .canAcceptStagedTournamentResult() - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - if can_accept.isTournamentResultStaged && can_accept.isClaimStagingPeriodOver { - trace!( - "epoch {} already has a staged tournament result past its staging period", - epoch_number - ); - return Ok(()); - } - - match self.state_manager.settlement_info( - epoch_number - .to_u64() - .expect("fail to convert epoch number to u64"), - )? { - Some(settlement) => { - let claim = vec_u8_to_bytes_32(settlement.final_state.into()); - - info!("submit sentry claim {} for epoch {}", claim, epoch_number); - let tx_result = dave_consensus - .submitSentryClaim(epoch_number, claim) - .send() - .await; - - allow_revert_rethrow_others("submitSentryClaim", tx_result).await?; - } - None => { - trace!("wait for the `machine-runner` to insert the value"); - } - } - Ok(()) - } - - async fn try_stage_tournament_result( - &mut self, - dave_consensus: &DaveConsensus::DaveConsensusInstance< - DynProvider, - alloy::network::Ethereum, - >, - ) -> Result<()> { - let can_stage = dave_consensus - .canStageTournamentResult() - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - if can_stage.isFinished && !can_stage.isTournamentResultStaged { - match self.state_manager.settlement_info( - can_stage - .epochNumber - .to_u64() - .expect("fail to convert epoch number to u64"), - )? { - Some(settlement) => { - assert_eq!( - settlement.computation_hash.data(), - can_stage.winnerCommitment, - "Winner commitment mismatch, notify all users!" - ); - assert_eq!( - settlement.final_state, can_stage.winnerPostEpochMachineStateHash, - "Winner final state mismatch, notify all users!" - ); - info!( - "stage tournament result of epoch {} with claim {}", - can_stage.epochNumber, - settlement.computation_hash.to_hex() - ); - let tx_result = dave_consensus - .stageTournamentResult( - can_stage.epochNumber, - vec_u8_to_bytes_32(settlement.output_merkle.into()), - to_bytes_32_vec(settlement.output_proof), - ) - .send() - .await; - allow_revert_rethrow_others("stageTournamentResult", tx_result).await?; - } - None => { - trace!("wait for the `machine-runner` to insert the value"); - } - } - } else { - trace!("tournament result not ready to be staged"); - } - Ok(()) - } - - async fn try_accept_tournament_result( - &mut self, - dave_consensus: &DaveConsensus::DaveConsensusInstance< - DynProvider, - alloy::network::Ethereum, - >, - ) -> Result<()> { - let can_accept = dave_consensus - .canAcceptStagedTournamentResult() - .block(alloy::eips::BlockId::pending()) - .call() - .await?; - - if can_accept.isTournamentResultStaged { - match self.state_manager.settlement_info( - can_accept - .epochNumber - .to_u64() - .expect("fail to convert epoch number to u64"), - )? { - Some(settlement) => { - assert_eq!( - vec_u8_to_bytes_32(settlement.final_state.into()), - can_accept.stagedPostEpochMachineStateHash, - "Staged final state mismatch, notify all users!" - ); - assert_eq!( - vec_u8_to_bytes_32(settlement.output_merkle.into()), - can_accept.stagedPostEpochOutputsMerkleRoot, - "Staged outputs Merkle root mismatch, notify all users!" - ); - if can_accept.doAllSentriesAgreeWithStagedTournamentResult - || can_accept.isClaimStagingPeriodOver - { - info!( - "accept staged tournament result of epoch {}", - can_accept.epochNumber - ); - let tx_result = dave_consensus - .acceptStagedTournamentResult(can_accept.epochNumber) - .send() - .await; - allow_revert_rethrow_others("acceptStagedTournamentResult", tx_result) - .await?; - } - } - None => { - trace!("wait for the `machine-runner` to insert the value"); - } - } - } else { - trace!("staged tournament result not ready to be accepted"); - } - Ok(()) - } - - async fn try_react_epoch(&mut self, provider: DynProvider) -> Result<()> { - // participate in last sealed epoch tournament - if let Some(last_sealed_epoch) = self.state_manager.last_sealed_epoch()? { - match self - .state_manager - .settlement_info(last_sealed_epoch.epoch_number)? - { - Some(_) => { - trace!( - "dispute tournaments for epoch {}", - last_sealed_epoch.epoch_number - ); - self.react_dispute(provider, &last_sealed_epoch).await? - } - None => { - debug!( - "wait for `machine-runner` to insert settlement values for epoch {}", - last_sealed_epoch.epoch_number - ); - } - } - } - Ok(()) - } - - async fn react_dispute( - &mut self, - provider: DynProvider, - last_sealed_epoch: &Epoch, - ) -> Result<()> { - self.get_latest_player(last_sealed_epoch, provider)?; - self.last_react_epoch - .0 - .as_mut() - .expect("prt player should be instantiated") - .react() - .await?; - - Ok(()) - } - - fn get_latest_player( - &mut self, - last_sealed_epoch: &Epoch, - provider: DynProvider, - ) -> Result<()> { - let snapshot = self - .state_manager - .snapshot_dir(last_sealed_epoch.epoch_number, 0)? - .expect("snapshot is inserted atomically with settlement info"); - - // either the player has never been instantiated, or the sealed epoch has advanced - // we need to instantiate new epoch player with appropriate data - if self.last_react_epoch.0.is_none() - || self.last_react_epoch.1 != last_sealed_epoch.epoch_number - { - let inputs = self - .state_manager - .inputs(last_sealed_epoch.epoch_number)? - .into_iter() - .map(Input) - .collect(); - - let leafs = self - .state_manager - .epoch_state_hashes(last_sealed_epoch.epoch_number)? - .into_iter() - .map(|l| Leaf { - hash: l.hash, - repetitions: l.repetitions, - }) - .collect(); - - let player = Player::new( - self.arena_sender.clone(), - inputs, - leafs, - provider.erased(), - snapshot.to_string_lossy().to_string(), - last_sealed_epoch.root_tournament, - last_sealed_epoch.block_created_number, - self.long_block_range_error_codes.clone(), - self.state_manager - .epoch_directory(last_sealed_epoch.epoch_number)?, - ) - .expect("fail to initialize prt player"); - - self.last_react_epoch = (Some(player), last_sealed_epoch.epoch_number); - } - - Ok(()) - } -} - -fn to_bytes_32_vec(proof: Proof) -> Vec { - proof.inner().iter().map(B256::from).collect() -} - -fn vec_u8_to_bytes_32(hash: Vec) -> B256 { - B256::from_slice(&hash) -} diff --git a/cartesi-rollups/node/machine-runner/Cargo.toml b/cartesi-rollups/node/machine-runner/Cargo.toml deleted file mode 100644 index 499ef383e..000000000 --- a/cartesi-rollups/node/machine-runner/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "rollups-machine-runner" -version.workspace = true -authors.workspace = true -description.workspace = true -edition.workspace = true -homepage.workspace = true -license-file.workspace = true -readme.workspace = true -repository.workspace = true - -[dependencies] -alloy = { workspace = true } -cartesi-dave-merkle = { workspace = true } -cartesi-prt-core = { workspace = true } -cartesi-machine = { workspace = true } -rollups-state-manager = { workspace = true } - -thiserror = { workspace = true } -log = { workspace = true } - -[dev-dependencies] -cartesi-rollups-contracts = { workspace = true } -hex = "0.4.3" diff --git a/cartesi-rollups/node/machine-runner/src/lib.rs b/cartesi-rollups/node/machine-runner/src/lib.rs deleted file mode 100644 index adef53a44..000000000 --- a/cartesi-rollups/node/machine-runner/src/lib.rs +++ /dev/null @@ -1,93 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pub mod error; - -use error::Result; -use std::{ops::ControlFlow, time::Duration}; - -use cartesi_machine::types::cmio::ManualReason; -use rollups_state_manager::{InputId, StateManager, sync::Watch}; -pub struct MachineRunner { - state_manager: SM, - sleep_duration: Duration, -} - -impl MachineRunner { - pub fn new(state_manager: SM, sleep_duration: Duration) -> Result { - Ok(Self { - state_manager, - sleep_duration, - }) - } - - pub fn start(&mut self, watch: Watch) -> Result<()> { - loop { - self.process_rollup()?; - - // all inputs have been processed up to this point, - // sleep and come back later - if matches!(watch.wait(self.sleep_duration), ControlFlow::Break(_)) { - break Ok(()); - } - } - } - - fn process_rollup(&mut self) -> Result<()> { - // process all inputs that are currently availalble - loop { - self.catch_up()?; - - let current_machine_epoch = self.state_manager.next_input_id()?.epoch_number; - let latest_blockchain_epoch = self.state_manager.epoch_count()?; - - if current_machine_epoch == latest_blockchain_epoch { - // all current inputs processed in current epoch, which is still open. - // sleep and come back later. - break Ok(()); - } else { - // epoch is finished, all inputs processed - assert!(current_machine_epoch < latest_blockchain_epoch); - self.state_manager.roll_epoch()?; - log::info!("started new epoch {}", current_machine_epoch + 1); - } - } - } - - fn catch_up(&mut self) -> Result<()> { - let mut rollups_machine = self.state_manager.latest_snapshot()?; - - loop { - let next_input_index = rollups_machine.next_input_index_in_epoch(); - - let input_id = InputId { - epoch_number: rollups_machine.epoch(), - input_index_in_epoch: next_input_index, - }; - let input = self.state_manager.input(&input_id)?; - - match input { - Some(input) => { - log::info!( - "processing input {}:{}", - input.id.epoch_number, - input.id.input_index_in_epoch - ); - let (state_hashes, reason) = rollups_machine.process_input(&input.data)?; - - match reason { - ManualReason::RxAccepted { .. } => { - self.state_manager - .advance_accepted(&mut rollups_machine, &state_hashes)?; - } - _ => { - self.state_manager - .advance_reverted(&mut rollups_machine, &state_hashes)?; - } - } - } - None => break Ok(()), - } - } - } -} diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs b/cartesi-rollups/node/src/args.rs similarity index 80% rename from cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs rename to cartesi-rollups/node/src/args.rs index 3f6492b55..957d42652 100644 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/args.rs +++ b/cartesi-rollups/node/src/args.rs @@ -1,13 +1,11 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +use crate::blockchain_reader::AddressBook; +use crate::storage::{Storage, StorageError}; use alloy::{primitives::Address, providers::DynProvider, transports::http::reqwest::Url}; use alloy_chains::NamedChain; use clap::{ArgGroup, Parser, Subcommand}; -use rollups_blockchain_reader::AddressBook; -use rollups_state_manager::{ - StateAccessError, StateManager, persistent_state_access::PersistentStateAccess, -}; use std::{fmt, path::PathBuf, time::Duration}; use crate::provider::create_provider; @@ -43,6 +41,11 @@ pub struct PRTArgs { #[arg(long, env, default_value_t = SLEEP_DURATION)] pub sleep_duration_seconds: u64, + /// keep every Nth input-boundary machine snapshot (1 keeps all); + /// the disk-vs-dispute-replay knob + #[arg(long, env, default_value_t = crate::storage::DEFAULT_SNAPSHOT_GAP_INPUTS)] + pub snapshot_gap_inputs: u64, + #[arg(long, env, default_value_os_t = std::env::temp_dir())] pub state_dir: PathBuf, @@ -98,7 +101,7 @@ pub enum SignerArgs { } #[derive(Clone)] -pub struct PRTConfig { +pub struct NodeConfig { // App pub address_book: AddressBook, pub machine_path: PathBuf, @@ -114,12 +117,13 @@ pub struct PRTConfig { // Misc pub sleep_duration: Duration, pub long_block_range_error_codes: Vec, + pub snapshot_gap_inputs: u64, // private signer: SignerArgs, } -impl fmt::Display for PRTConfig { +impl fmt::Display for NodeConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.address_book)?; writeln!(f, "Machine path: {}", self.machine_path.display())?; @@ -144,17 +148,18 @@ impl fmt::Display for PRTConfig { } } -impl PRTConfig { - pub fn setup() -> (Self, PersistentStateAccess) { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("`PRTConfig::setup` runtime build failure"); - rt.block_on(async move { Self::_setup().await }) +impl NodeConfig { + pub fn storage(&self) -> Result { + let mut access = Storage::new(&self.state_dir)?; + access.set_snapshot_gap_inputs(self.snapshot_gap_inputs); + Ok(access) } - pub fn state_access(&self) -> Result { - PersistentStateAccess::new(&self.state_dir) + /// For workers that only read through their own handle (the + /// epoch manager; the dispute Hero opens its own read-write + /// Storage). Fails fast under write pressure instead of stalling. + pub fn storage_read_only(&self) -> Result { + Storage::open_read_only(&self.state_dir) } pub async fn provider(&self) -> DynProvider { @@ -163,7 +168,7 @@ impl PRTConfig { .1 } - async fn _setup() -> (Self, PersistentStateAccess) { + pub async fn setup() -> (Self, Storage) { let args = PRTArgs::parse(); let chain_id = args @@ -175,14 +180,15 @@ impl PRTConfig { create_provider(&args.web3_rpc_url, chain_id, &args.signer).await; let address_book = AddressBook::new(args.app_address, &provider).await; - let mut state_manager = PersistentStateAccess::migrate( + let mut storage = Storage::migrate( &args.state_dir, &args.machine_path, address_book.genesis_block_number, + address_book.app, ) - .expect("could not create `state_manager`"); + .expect("could not create `storage`"); - let mut machine = state_manager + let mut machine = storage .snapshot(0, 0) .unwrap() .expect("epoch zero should always exist"); @@ -195,7 +201,7 @@ impl PRTConfig { ( Self { address_book, - state_dir: state_manager.state_dir().to_owned(), + state_dir: storage.state_dir().to_owned(), machine_path: args.machine_path, chain_id, signer_address, @@ -203,8 +209,9 @@ impl PRTConfig { sleep_duration: Duration::from_secs(args.sleep_duration_seconds), signer: args.signer, long_block_range_error_codes: args.long_block_range_error_codes, + snapshot_gap_inputs: args.snapshot_gap_inputs, }, - state_manager, + storage, ) } } diff --git a/common-rs/arithmetic/src/lib.rs b/cartesi-rollups/node/src/arithmetic.rs similarity index 81% rename from common-rs/arithmetic/src/lib.rs rename to cartesi-rollups/node/src/arithmetic.rs index 960e15b32..17e813160 100644 --- a/common-rs/arithmetic/src/lib.rs +++ b/cartesi-rollups/node/src/arithmetic.rs @@ -4,5 +4,5 @@ pub const fn max_uint(k: u64) -> u64 { } pub fn add_and_clamp(x: u64, y: u64) -> u64 { - x.checked_add(y).unwrap_or(u64::MAX) + x.saturating_add(y) } diff --git a/cartesi-rollups/node/src/bin/measure.rs b/cartesi-rollups/node/src/bin/measure.rs new file mode 100644 index 000000000..8bf625ca5 --- /dev/null +++ b/cartesi-rollups/node/src/bin/measure.rs @@ -0,0 +1,988 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The measurement harness (docs/plans/node-refactor.md, workstream 1): +//! times the operations dispute deadlines depend on and regenerates +//! docs/plans/measurements.md. Run through `just measure`; committing a +//! regenerated table is a reviewed act, fixtures-style. + +use anyhow::Result; +use clap::Parser; +use std::fmt::Write as _; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; + +use alloy::primitives::{Address, U256}; +use alloy::sol_types::SolCall; +use cartesi_rollups_prt_node::engine::{DisputeSource, MachineStf, Quartet, Stf, fold_runs}; +use cartesi_rollups_prt_node::merkle::Digest; +use cartesi_rollups_prt_node::storage::{Input as StorageInput, InputId, Storage}; + +/// Five minutes of clock per tree height unit: the deployment's +/// matchEffort formula (prt/contracts/script/Deployment.s.sol, +/// _getMatchEffortInSeconds). Every replay a bisection move needs must +/// fit well inside this. +const PER_MOVE_BUDGET_SECS: u64 = 300; + +#[derive(Parser)] +struct Args { + /// Machine template image (built by `just setup-local`). + #[arg(long, default_value = "test/programs/echo/machine-image")] + machine: PathBuf, + + /// Write the report here instead of stdout. + #[arg(long)] + out: Option, + + /// Include the level-1 root replay: a full 2^44-ustep window span, + /// potentially minutes of machine time. + #[arg(long)] + full: bool, + + /// Derive tournament level constants (workstream 8 of + /// docs/plans/node-refactor.md) instead of the baseline report. + /// Needs a compute-heavy workload (the stress image). + #[arg(long)] + constants: bool, + + /// Accepted slowdown of the root-level eager commitment + /// (docs/dimensioning.md: an aggregate authored by the trusted + /// app, so priced at average density). + #[arg(long, default_value_t = 2.0)] + root_slowdown: f64, + + /// Inner tournament timeouts (minutes) to derive for. + #[arg(long, value_delimiter = ',', default_values_t = vec![60u64, 30])] + inner_timeout_minutes: Vec, + + /// Pragmatic stand-in for a reference machine: measured throughput + /// is divided by this before any derivation, and the factor is + /// printed into the output so results carry their caveat. + #[arg(long, default_value_t = 2.0)] + hardware_slack: f64, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let image = args.machine.canonicalize()?; + let scratch_root = std::env::temp_dir().join(format!("dave-measure-{}", std::process::id())); + fs::create_dir_all(&scratch_root)?; + + if args.constants { + let mut report = String::new(); + constants_report(&mut report, &args, &image, &scratch_root)?; + let _ = fs::remove_dir_all(&scratch_root); + match args.out { + Some(path) => { + fs::write(&path, &report)?; + eprintln!("wrote {}", path.display()); + } + None => print!("{report}"), + } + return Ok(()); + } + + let mut report = String::new(); + preamble(&mut report, &image, args.full)?; + bench_level0_fold(&mut report)?; + bench_snapshot(&mut report, &image, &scratch_root)?; + bench_clone_loop(&mut report, &image, &scratch_root)?; + bench_atoms(&mut report, &image, &scratch_root)?; + let quartets = bench_quartets(&mut report, &image, &scratch_root, args.full)?; + budget(&mut report, &quartets)?; + + let _ = fs::remove_dir_all(&scratch_root); + match args.out { + Some(path) => { + fs::write(&path, &report)?; + eprintln!("wrote {}", path.display()); + } + None => print!("{report}"), + } + Ok(()) +} + +fn preamble(report: &mut String, image: &Path, full: bool) -> Result<()> { + writeln!(report, "# Measurement baseline")?; + writeln!(report)?; + writeln!( + report, + "Generated by `just measure` (cartesi-rollups/node/src/bin/measure.rs);\n\ + regenerate on the machine that matters and commit the diff. One\n\ + sample per operation - treat entries as order-of-magnitude until\n\ + the harness grows repetitions and percentiles." + )?; + writeln!(report)?; + writeln!(report, "Workload: `{}`.", image.display())?; + writeln!( + report, + "Caveats: the echo workload is idle-dominated (it yields almost\n\ + immediately), so span replays here exercise the idle-churn path;\n\ + the compute-heavy program (workstream 2b) supplies the missing\n\ + worst-case rows. Not yet measured: get_logs probe, RSS per\n\ + worker, disk breakdown per epoch (logged node-side at roll).{}", + if full { + "" + } else { + "\nLevel-1 root replay skipped (run with --full)." + } + )?; + writeln!(report)?; + Ok(()) +} + +/// Worst-case folds: alternating distinct hashes, no adjacent-run +/// merging, tail-padded to one 2^24-leaf tier - the shape of the +/// frontier's top fold (window roots plus padding) and of the +/// per-window fold the runner pays at each record. The 1M-run row is +/// the OQ9 corner, now amortized one window per input instead of a +/// whole-epoch fold at every Hero construction. +fn bench_level0_fold(report: &mut String) -> Result<()> { + writeln!( + report, + "## Level-0 fold (synthetic runs, one 2^24-leaf tier)" + )?; + writeln!(report)?; + writeln!(report, "| runs | fold time |")?; + writeln!(report, "|---:|---:|")?; + const LOG2_LEAVES: u64 = 24; // window interior = top tree = 2^24 + for &count in &[1_000u64, 10_000, 100_000, 1_000_000] { + let total: u64 = 1 << LOG2_LEAVES; + let runs = (0..count).map(move |i| { + let mut bytes = [0u8; 32]; + bytes[..8].copy_from_slice(&i.to_le_bytes()); + bytes[8] = 1; + let repetitions = if i == count - 1 { + total - (count - 1) + } else { + 1 + }; + (Digest::from_digest(&bytes).expect("32 bytes"), repetitions) + }); + let (_, elapsed) = timed(|| fold_runs(runs, LOG2_LEAVES))?; + writeln!(report, "| {count} | {} |", fmt_duration(elapsed))?; + } + writeln!(report)?; + Ok(()) +} + +fn bench_snapshot(report: &mut String, image: &Path, scratch_root: &Path) -> Result<()> { + let (mut stf, load_template) = + timed(|| MachineStf::load(image, scratch(scratch_root, "snap-load")?))?; + let store_path = scratch_root.join("stored-machine"); + let (_, store) = timed(|| stf.store(&store_path))?; + let (_, resume) = + timed(|| MachineStf::resume(&store_path, scratch(scratch_root, "snap-resume")?))?; + let size_mb = dir_size(&store_path)? as f64 / (1024.0 * 1024.0); + + writeln!(report, "## Snapshot store and load")?; + writeln!(report)?; + writeln!(report, "| operation | time |")?; + writeln!(report, "|---|---:|")?; + writeln!( + report, + "| load template | {} |", + fmt_duration(load_template) + )?; + writeln!(report, "| store | {} |", fmt_duration(store))?; + writeln!(report, "| resume from store | {} |", fmt_duration(resume))?; + writeln!(report, "| stored size | {size_mb:.1} MB |")?; + writeln!(report)?; + Ok(()) +} + +/// The CoW clone loop (docs/plans/snapshots.md): the per-input cost +/// of clone -> load SHARING_ALL -> advance -> root_hash -> destroy, +/// the physical cost of each kept boundary, and the mapping-mode A/B +/// for the hash-hot sampling loop. Boundary cost is a free-space +/// delta: order of magnitude only (any concurrent writer moves it), +/// but immune to the shared-extent overcounting that breaks du on +/// reflinked files. On a filesystem without reflinks the loop +/// degrades to sparse copies and these rows price exactly that. +fn bench_clone_loop(report: &mut String, image: &Path, scratch_root: &Path) -> Result<()> { + use cartesi_machine::config::runtime::RuntimeConfig; + use cartesi_machine::machine::Machine; + use cartesi_machine::types::SharingMode; + + let chain_root = scratch(scratch_root, "clone-chain")?; + let boundary = |k: u64| chain_root.join(format!("boundary-{k}")); + let (_, template_clone) = timed(|| Ok(Machine::clone_stored(image, &boundary(0))?))?; + + writeln!(report, "## The clone loop (docs/plans/snapshots.md)")?; + writeln!(report)?; + writeln!( + report, + "Chain of clones over echo inputs: clone the previous boundary,\n\ + load SHARING_ALL, advance one input, root_hash (sidecars exact),\n\ + destroy. Boundary cost is the free-space delta of one whole\n\ + iteration - what keeping that boundary physically costs.\n\ + Template clone: {}.", + fmt_duration(template_clone) + )?; + writeln!(report)?; + writeln!( + report, + "| input | clone | load | advance | root_hash | destroy | boundary cost |" + )?; + writeln!(report, "|---:|---:|---:|---:|---:|---:|---:|")?; + + const INPUTS: u64 = 4; + for k in 0..INPUTS { + let working = chain_root.join("working"); + let free_before = free_space_kb(&chain_root)?; + let (_, clone) = timed(|| Ok(Machine::clone_stored(&boundary(k), &working)?))?; + let (machine, load) = timed(|| { + Ok(Machine::load_with_sharing( + &working, + &RuntimeConfig::quiet_console(), + SharingMode::All, + )?) + })?; + let mut machine = machine; + let input = evm_advance_input(k, b"measure"); + let (_, advance) = timed(|| advance_one_input(&mut machine, &input))?; + let (_, hash) = timed(|| Ok(machine.root_hash()?))?; + let (_, destroy) = timed(|| { + drop(machine); + Ok(()) + })?; + fs::rename(&working, boundary(k + 1))?; + let free_after = free_space_kb(&chain_root)?; + let churn_mb = (free_before as i64 - free_after as i64) as f64 / 1024.0; + + writeln!( + report, + "| {k} | {} | {} | {} | {} | {} | {churn_mb:.1} MB |", + fmt_duration(clone), + fmt_duration(load), + fmt_duration(advance), + fmt_duration(hash), + fmt_duration(destroy), + )?; + } + writeln!(report)?; + + // The hash-hot sampling loop under each mapping mode: does + // MAP_SHARED slow the ustep + root_hash pair the level-2 collect + // lives in? A fresh clone per mode (ALL locks and mutates its + // directory). + writeln!(report, "| hash-hot pairs (uarch step + root_hash) | rate |")?; + writeln!(report, "|---|---:|")?; + for (tag, label, mode) in [ + ("private", "private mapping (CONFIG)", SharingMode::Config), + ("shared", "shared mapping (ALL)", SharingMode::All), + ] { + let dir = chain_root.join(format!("pairs-{tag}")); + Machine::clone_stored(&boundary(INPUTS), &dir)?; + let mut machine = Machine::load_with_sharing(&dir, &RuntimeConfig::quiet_console(), mode)?; + let pairs = 500u64; + let start = Instant::now(); + for _ in 0..pairs { + if machine.uarch_halt_flag()? { + machine.reset_uarch()?; + } else { + let ucycle = machine.ucycle()?; + machine.run_uarch(ucycle + 1)?; + } + machine.root_hash()?; + } + let elapsed = start.elapsed(); + writeln!( + report, + "| {label} | {:.0}/s |", + pairs as f64 / elapsed.as_secs_f64() + )?; + } + writeln!(report)?; + + Ok(()) +} + +/// One input through a raw machine, the advance path's shape minus +/// leaf collection: checkpoint write, cmio delivery, run to the next +/// manual yield. +fn advance_one_input(machine: &mut cartesi_machine::machine::Machine, input: &[u8]) -> Result<()> { + use cartesi_machine::constants::break_reason; + use cartesi_machine::types::cmio::CmioResponseReason; + use cartesi_rollups_prt_node::engine::constants::CHECKPOINT_ADDRESS; + + anyhow::ensure!(machine.iflags_y()?, "machine must be awaiting input"); + let checkpoint = machine.root_hash()?; + machine.write_memory(CHECKPOINT_ADDRESS, &checkpoint)?; + machine.send_cmio_response(CmioResponseReason::Advance, input)?; + loop { + match machine.run(u64::MAX)? { + break_reason::YIELDED_AUTOMATICALLY | break_reason::YIELDED_SOFTLY => continue, + break_reason::YIELDED_MANUALLY => break Ok(()), + reason => anyhow::bail!("unexpected break reason {reason}"), + } + } +} + +/// Available space of the filesystem holding `path`, in KB (df). +fn free_space_kb(path: &Path) -> Result { + let out = std::process::Command::new("df") + .arg("-k") + .arg(path) + .output()?; + anyhow::ensure!(out.status.success(), "df failed"); + let text = String::from_utf8_lossy(&out.stdout); + let row = text + .lines() + .nth(1) + .ok_or_else(|| anyhow::anyhow!("df: no data row"))?; + let avail = row + .split_whitespace() + .nth(3) + .ok_or_else(|| anyhow::anyhow!("df: no available column"))?; + Ok(avail.parse()?) +} + +/// The primitive rates every extrapolation is built from, measured on +/// the real machine: idle churn (the ustep/ureset cycle a yielded +/// machine burns per big cycle), the input feed, active usteps, and +/// the ustep+state_hash pair that level-2 sampling pays per leaf. +fn bench_atoms(report: &mut String, image: &Path, scratch_root: &Path) -> Result<()> { + let input = evm_advance_input(0, b"measure"); + let mut stf = + MachineStf::load(image, scratch(scratch_root, "atoms")?)?.with_inputs(vec![input]); + + // Idle churn on the pristine yielded machine. Counts real usteps + // (ustep is identity once the uarch halts, so drive whole cycles). + let idle_cycles = 2_000u64; + let mut idle_usteps = 0u64; + let start = Instant::now(); + let mut cycles = 0u64; + while cycles < idle_cycles { + if stf.uarch_halted()? { + stf.ureset()?; + cycles += 1; + } else { + stf.ustep()?; + idle_usteps += 1; + } + } + let idle_elapsed = start.elapsed(); + let idle_per_cycle = idle_usteps as f64 / idle_cycles as f64; + + // One real input feed (the fused transition's expensive half). + let (_, feed) = timed(|| stf.feed(0))?; + + // Active usteps: the fed input gives the uarch real work. + let active_usteps = 200_000u64; + let start = Instant::now(); + let mut done = 0u64; + while done < active_usteps { + if stf.uarch_halted()? { + stf.ureset()?; + } else { + stf.ustep()?; + done += 1; + } + } + let active_elapsed = start.elapsed(); + + // The level-2 sampling workload: every ustep dirties state, every + // sample pays a root hash. + let pairs = 500u64; + let start = Instant::now(); + for _ in 0..pairs { + if stf.uarch_halted()? { + stf.ureset()?; + } else { + stf.ustep()?; + } + stf.state_hash()?; + } + let pairs_elapsed = start.elapsed(); + + writeln!(report, "## Machine atoms")?; + writeln!(report)?; + writeln!(report, "| atom | rate |")?; + writeln!(report, "|---|---:|")?; + writeln!( + report, + "| idle big cycles (churn + ureset) | {:.0}/s ({:.1} usteps/cycle) |", + idle_cycles as f64 / idle_elapsed.as_secs_f64(), + idle_per_cycle, + )?; + writeln!(report, "| input feed | {} |", fmt_duration(feed))?; + writeln!( + report, + "| active usteps | {:.2} M/s |", + active_usteps as f64 / active_elapsed.as_secs_f64() / 1e6, + )?; + writeln!( + report, + "| ustep + state_hash pair | {:.0}/s |", + pairs as f64 / pairs_elapsed.as_secs_f64(), + )?; + writeln!(report)?; + Ok(()) +} + +/// Real span replays through the facade's node(), each on a fresh +/// storage and factory (guaranteed miss), then the same quartet +/// again (hit). Returns (label, miss latency) rows for the budget +/// table. +fn bench_quartets( + report: &mut String, + image: &Path, + scratch_root: &Path, + full: bool, +) -> Result> { + let mut spans: Vec<(&str, u64, u64)> = vec![ + ("uarch span", 0, 20), + ("mid stride", 27, 10), + ("coarse", 44, 4), + ("level-2 root shape", 0, 27), + ]; + if full { + spans.push(("level-1 root shape", 27, 17)); + } + + let inputs = [ + evm_advance_input(0, b"hello dave"), + evm_advance_input(1, b"hello again, dave"), + ]; + + let workload = image + .parent() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_else(|| "unknown".into()); + writeln!( + report, + "## Span replays (get_or_compute, two-input {workload} epoch)" + )?; + writeln!(report)?; + writeln!(report, "| span | quartet | miss | cache hit |")?; + writeln!(report, "|---|---|---:|---:|")?; + + let mut results = Vec::new(); + for (index, (label, log2_stride, height)) in spans.into_iter().enumerate() { + let state_dir = scratch(scratch_root, &format!("quartet-{index}"))?; + let mut storage = Storage::migrate(&state_dir, image, 0, Address::ZERO)?; + let rows: Vec = inputs + .iter() + .enumerate() + .map(|(i, data)| StorageInput { + id: InputId { + epoch_number: 0, + input_index_in_epoch: i as u64, + }, + data: data.clone(), + }) + .collect(); + storage.insert_consensus_data(0, rows.iter(), std::iter::empty())?; + let mut source = DisputeSource::on_store( + storage, + 0, + scratch(scratch_root, &format!("quartet-{index}-work"))?, + )?; + let quartet = Quartet::level_root(0, log2_stride, height); + + let (_, miss) = timed(|| source.node(&quartet))?; + let (_, hit) = timed(|| source.node(&quartet))?; + writeln!( + report, + "| {label} | r{log2_stride} h{height} | {} | {} |", + fmt_duration(miss), + fmt_duration(hit), + )?; + results.push((label.to_string(), log2_stride, height, miss)); + } + writeln!(report)?; + Ok(results) +} + +fn budget(report: &mut String, quartets: &[(String, u64, u64, Duration)]) -> Result<()> { + writeln!(report, "## Clock budget")?; + writeln!(report)?; + writeln!( + report, + "matchEffort grants five minutes of clock per height unit\n\ + (Deployment.s.sol), so a bisection move budgets ~{PER_MOVE_BUDGET_SECS} s.\n\ + Total allowances: devnet 1 h, testnet 9 h, mainnet 1 week + 1 h.\n\ + Level 0 never replays (seed-served); levels 1 and 2 pay their\n\ + root-shape replay on the first cold descent." + )?; + writeln!(report)?; + writeln!( + report, + "| level | root span | measured (this workload) | budget | margin |" + )?; + writeln!(report, "|---|---|---:|---:|---:|")?; + for (level, stride, height) in [(1u64, 27u64, 17u64), (2, 0, 27)] { + let row = quartets + .iter() + .find(|(_, s, h, _)| *s == stride && *h == height); + let (measured, margin) = match row { + Some((_, _, _, d)) => { + let secs = d.as_secs_f64(); + ( + fmt_duration(*d), + format!("{:.0}x", PER_MOVE_BUDGET_SECS as f64 / secs.max(1e-9)), + ) + } + None => ("not measured".into(), "-".into()), + }; + writeln!( + report, + "| {level} | 2^{} usteps | {measured} | {PER_MOVE_BUDGET_SECS} s | {margin} |", + stride + height, + )?; + } + writeln!(report)?; + Ok(()) +} + +/// The canonical input encoding (Inputs.sol EvmAdvance), mirroring the +/// differential tests; raw bytes would crash the rollup driver. +fn evm_advance_input(index: u64, payload: &[u8]) -> Vec { + alloy::sol! { + function EvmAdvance( + uint256 chainId, + address appContract, + address msgSender, + uint256 blockNumber, + uint256 blockTimestamp, + uint256 prevRandao, + uint256 index, + bytes memory payload + ) external; + } + EvmAdvanceCall { + chainId: U256::from(31337), + appContract: Address::ZERO, + msgSender: Address::ZERO, + blockNumber: U256::from(1), + blockTimestamp: U256::from(1), + prevRandao: U256::from(0), + index: U256::from(index), + payload: payload.to_vec().into(), + } + .abi_encode() +} + +fn timed(f: impl FnOnce() -> Result) -> Result<(T, Duration)> { + let start = Instant::now(); + let value = f()?; + Ok((value, start.elapsed())) +} + +fn scratch(root: &Path, tag: &str) -> Result { + let path = root.join(tag); + fs::create_dir_all(&path)?; + Ok(path) +} + +fn dir_size(path: &Path) -> Result { + let mut total = 0; + for entry in fs::read_dir(path)? { + let entry = entry?; + let meta = entry.metadata()?; + total += if meta.is_dir() { + dir_size(&entry.path())? + } else { + meta.len() + }; + } + Ok(total) +} + +fn fmt_duration(d: Duration) -> String { + let secs = d.as_secs_f64(); + if secs >= 1.0 { + format!("{secs:.2} s") + } else if secs >= 1e-3 { + format!("{:.1} ms", secs * 1e3) + } else { + format!("{:.0} us", secs * 1e6) + } +} + +// +// The constants pipeline (--constants): workstream 8 of +// docs/plans/node-refactor.md, superseding prt/measure_constants. +// Fixes that port carries (docs/dimensioning.md, measurement +// discipline): halt AND yield guarded on every timed region, +// steady-state input-fed sampling instead of boot, conservative +// floor rounding instead of floor+1. +// + +const LOG2_UARCH: u64 = 20; +const LOG2_RULER: u64 = 92; +const CURRENT_LOG2STEP: [u64; 3] = [44, 27, 0]; +const CURRENT_HEIGHT: [u64; 3] = [48, 17, 27]; + +/// Steady-state rates plus the hash-cost curve, all measured +/// mid-computation on a fed machine. +struct SteadyAtoms { + avg_usteps_per_big: f64, + dense_pairs_per_sec: f64, + /// (delta in big cycles, median run time, median hash time). + curve: Vec<(u64, Duration, Duration)>, +} + +/// A machine kept in active computation: re-feeds inputs as the +/// workload consumes them, and refuses to let any timed region see a +/// yielded or halted state. +struct ActiveMachine { + stf: MachineStf, + inputs: Vec>, + next_input: usize, +} + +impl ActiveMachine { + fn load(image: &Path, scratch_root: &Path) -> Result { + let inputs: Vec> = (0..16) + .map(|i| evm_advance_input(i, b"constants")) + .collect(); + let stf = MachineStf::load(image, scratch(scratch_root, "constants")?)? + .with_inputs(inputs.clone()); + let mut this = Self { + stf, + inputs, + next_input: 0, + }; + this.ensure_active()?; + Ok(this) + } + + /// Feeds the next input if the workload yielded, then skips the + /// input handler's prologue so sampling sees the workload proper. + fn ensure_active(&mut self) -> Result<()> { + anyhow::ensure!( + !self.stf.halted()?, + "machine halted; --constants needs a yielding compute workload" + ); + if self.stf.yielded()? { + anyhow::ensure!( + self.next_input < self.inputs.len(), + "workload too light for --constants (exhausted {} inputs); use the stress image", + self.inputs.len(), + ); + let window = self.next_input as u64; + self.next_input += 1; + self.stf.feed(window)?; + let ran = self.stf.run_big(10_000)?; + anyhow::ensure!(ran == 10_000, "input's compute too small to sample"); + } + Ok(()) + } + + fn assert_active(&mut self, context: &str) -> Result<()> { + anyhow::ensure!( + !self.stf.yielded()? && !self.stf.halted()?, + "machine left the active state during {context}; workload too light" + ); + Ok(()) + } +} + +fn measure_steady_atoms(machine: &mut ActiveMachine) -> Result { + // Density and the dense pair rate: the leaf-level workload (hash + // after every executed ustep and every reset), over whole big + // cycles mid-computation. + machine.ensure_active()?; + let bigs_target = 500u64; + let mut usteps = 0u64; + let mut bigs = 0u64; + let start = Instant::now(); + while bigs < bigs_target { + if machine.stf.uarch_halted()? { + machine.stf.ureset()?; + bigs += 1; + } else { + machine.stf.ustep()?; + usteps += 1; + } + machine.stf.state_hash()?; + } + let dense_elapsed = start.elapsed(); + machine.assert_active("the dense sample")?; + let avg_usteps_per_big = usteps as f64 / bigs as f64; + let dense_pairs_per_sec = (usteps + bigs) as f64 / dense_elapsed.as_secs_f64(); + + // The hash-cost curve: per delta, clear the dirty set with an + // untimed hash, run delta big cycles, then time one root hash + // over the accumulated dirt. Samples that hit an input boundary + // are discarded, never timed short. + let mut curve = Vec::new(); + for log2_delta in (8..=24u64).step_by(2) { + let delta = 1u64 << log2_delta; + let mut runs = Vec::new(); + let mut hashes = Vec::new(); + let mut attempts = 0; + while runs.len() < 7 { + attempts += 1; + anyhow::ensure!( + attempts <= 24, + "workload too light to sample delta 2^{log2_delta}" + ); + machine.ensure_active()?; + machine.stf.state_hash()?; + let start = Instant::now(); + let ran = machine.stf.run_big(delta)?; + let run_time = start.elapsed(); + if ran < delta { + continue; + } + let start = Instant::now(); + machine.stf.state_hash()?; + hashes.push(start.elapsed()); + runs.push(run_time); + } + runs.sort(); + hashes.sort(); + curve.push((delta, runs[3], hashes[3])); + } + + Ok(SteadyAtoms { + avg_usteps_per_big, + dense_pairs_per_sec, + curve, + }) +} + +/// (run seconds, hash seconds) at an arbitrary delta: log-space linear +/// between measured points; run scales linearly below and above; hash +/// is flat below the first point (dirt is at least page-granular) and +/// scales linearly above the last (conservative: real dirt saturates). +fn interp_curve(curve: &[(u64, Duration, Duration)], delta: u64) -> (f64, f64) { + let pts: Vec<(f64, f64, f64)> = curve + .iter() + .map(|(d, r, h)| ((*d as f64).log2(), r.as_secs_f64(), h.as_secs_f64())) + .collect(); + let x = (delta as f64).log2(); + let (first, last) = (pts[0], pts[pts.len() - 1]); + if x <= first.0 { + let ratio = delta as f64 / 2f64.powf(first.0); + return (first.1 * ratio, first.2); + } + if x >= last.0 { + let ratio = delta as f64 / 2f64.powf(last.0); + return (last.1 * ratio, last.2 * ratio); + } + let i = pts.windows(2).position(|w| x <= w[1].0).unwrap(); + let (a, b) = (pts[i], pts[i + 1]); + let t = (x - a.0) / (b.0 - a.0); + (a.1 + t * (b.1 - a.1), a.2 + t * (b.2 - a.2)) +} + +struct Derived { + timeout_minutes: u64, + /// Top-down, ArbitrationConstants order. + log2step: Vec, + height: Vec, + root_slowdown: f64, +} + +fn derive( + atoms: &SteadyAtoms, + root_slowdown_budget: f64, + timeout_minutes: u64, + slack: f64, +) -> Result { + let budget_secs = (timeout_minutes * 60) as f64; + + // Leaf level: the tallest dense build that fits the timeout at the + // measured average density, hardware slack applied, floor rounded. + let dense_bigs_per_sec = atoms.dense_pairs_per_sec / (atoms.avg_usteps_per_big + 1.0) / slack; + let n_bigs = dense_bigs_per_sec * budget_secs; + anyhow::ensure!(n_bigs >= 2.0, "timeout too small for any leaf level"); + let h_leaf = LOG2_UARCH + n_bigs.log2().floor() as u64; + + let mut log2step = vec![0u64]; + let mut height = vec![h_leaf]; + let mut stride = h_leaf; + + let root_slowdown_at = |stride: u64| { + let d = 1u64 << (stride - LOG2_UARCH); + let (run_s, hash_s) = interp_curve(&atoms.curve, d); + (run_s + hash_s) / run_s + }; + + while root_slowdown_at(stride) > root_slowdown_budget { + anyhow::ensure!( + stride < LOG2_RULER, + "no stride within the ruler satisfies the slowdown budget" + ); + anyhow::ensure!(log2step.len() < 8, "runaway level stack"); + let d = 1u64 << (stride - LOG2_UARCH); + let (run_s, hash_s) = interp_curve(&atoms.curve, d); + let per_leaf = (run_s + hash_s) * slack; + let n = budget_secs / per_leaf; + anyhow::ensure!( + n >= 2.0, + "timeout too small for a level at stride 2^{stride}" + ); + let h = (n.log2().floor() as u64).min(LOG2_RULER - stride); + log2step.push(stride); + height.push(h); + stride += h; + } + anyhow::ensure!(stride < LOG2_RULER, "level stack consumed the whole ruler"); + + let root_slowdown = root_slowdown_at(stride); + log2step.push(stride); + height.push(LOG2_RULER - stride); + log2step.reverse(); + height.reverse(); + + Ok(Derived { + timeout_minutes, + log2step, + height, + root_slowdown, + }) +} + +fn constants_report( + report: &mut String, + args: &Args, + image: &Path, + scratch_root: &Path, +) -> Result<()> { + let mut machine = ActiveMachine::load(image, scratch_root)?; + let atoms = measure_steady_atoms(&mut machine)?; + + writeln!(report, "# Tournament constants derivation")?; + writeln!(report)?; + writeln!( + report, + "Generated by `just measure-constants` (measure.rs --constants),\n\ + superseding prt/measure_constants. Model: docs/dimensioning.md -\n\ + clocks price the trusted app's AVERAGE density; coordinates stay\n\ + worst-case. Every timed region asserts the machine is neither\n\ + yielded nor halted (the measure.lua audit's fixes); rounding is\n\ + floor, never floor+1." + )?; + writeln!(report)?; + writeln!( + report, + "Workload `{}`; root slowdown budget {}; hardware slack {} (divide-\n\ + measured-throughput stand-in for a reference machine).", + image.display(), + args.root_slowdown, + args.hardware_slack, + )?; + writeln!(report)?; + + writeln!(report, "## Steady-state atoms")?; + writeln!(report)?; + writeln!(report, "| atom | value |")?; + writeln!(report, "|---|---:|")?; + writeln!( + report, + "| executed usteps per big cycle (density label) | {:.1} |", + atoms.avg_usteps_per_big + )?; + writeln!( + report, + "| dense ustep+hash pairs | {:.0}/s |", + atoms.dense_pairs_per_sec + )?; + writeln!( + report, + "| dense big cycles (leaf-level build rate) | {:.0}/s |", + atoms.dense_pairs_per_sec / (atoms.avg_usteps_per_big + 1.0) + )?; + writeln!(report)?; + + writeln!( + report, + "## Hash-cost curve (dirt accumulated over delta big cycles)" + )?; + writeln!(report)?; + writeln!( + report, + "| delta (bigs) | stride | run | root hash | slowdown |" + )?; + writeln!(report, "|---:|---|---:|---:|---:|")?; + for (delta, run, hash) in &atoms.curve { + let slowdown = (run.as_secs_f64() + hash.as_secs_f64()) / run.as_secs_f64(); + writeln!( + report, + "| 2^{} | 2^{} | {} | {} | {:.2}x |", + delta.ilog2(), + delta.ilog2() as u64 + LOG2_UARCH, + fmt_duration(*run), + fmt_duration(*hash), + slowdown, + )?; + } + writeln!(report)?; + + writeln!(report, "## Derivations")?; + writeln!(report)?; + writeln!( + report, + "| inner timeout | levels | log2step | height | root slowdown |" + )?; + writeln!(report, "|---|---|---|---|---:|")?; + let mut any_tall_root = false; + for &timeout in &args.inner_timeout_minutes { + let d = derive(&atoms, args.root_slowdown, timeout, args.hardware_slack)?; + any_tall_root |= d.height[0] > CURRENT_HEIGHT[0]; + writeln!( + report, + "| {} min | {} | {:?} | {:?} | {:.2}x |", + d.timeout_minutes, + d.log2step.len(), + d.log2step, + d.height, + d.root_slowdown, + )?; + } + writeln!( + report, + "| (current) | 3 | {:?} | {:?} | - |", + CURRENT_LOG2STEP, CURRENT_HEIGHT + )?; + writeln!(report)?; + writeln!( + report, + "Heights always sum to 92, so matchEffort's five-minutes-per-\n\ + height-unit total is shape-invariant; level count changes only\n\ + the per-level join and nested-tournament overhead." + )?; + if any_tall_root { + writeln!(report)?; + writeln!( + report, + "A derived root height exceeds the current 48: verify contract-\n\ + side assumptions before adopting (tree math, position widths)." + )?; + } + writeln!(report)?; + + writeln!(report, "## Coordinated-bump checklist")?; + writeln!(report)?; + writeln!( + report, + "Constants changes are contract changes; time any bump against\n\ + the open audit. The bump touches, together:\n\ + ArbitrationConstants.sol (LEVELS, log2step, height);\n\ + rollups_machine::LOG2_STRIDE (= log2step(0));\n\ + docs/computation-hash.md's level table; harness fixtures.\n\ + Also wanted: a small test-shape profile so e2e disputes run in\n\ + seconds (node-refactor.md, workstream 8)." + )?; + writeln!(report)?; + writeln!(report, "## Caveats")?; + writeln!(report)?; + writeln!( + report, + "Single-machine, single-run numbers; the density label above is\n\ + this workload's, and clocks dimensioned here inherit the\n\ + trusted-app assumption (docs/dimensioning.md). The root\n\ + slowdown figure interpolates the curve's steepest band, so it\n\ + wobbles run to run - the derived level shape is the stable\n\ + output. Rerun on validator-grade hardware before adopting\n\ + anything." + )?; + Ok(()) +} diff --git a/cartesi-rollups/node/src/bin/record_chain.rs b/cartesi-rollups/node/src/bin/record_chain.rs new file mode 100644 index 000000000..658a63384 --- /dev/null +++ b/cartesi-rollups/node/src/bin/record_chain.rs @@ -0,0 +1,90 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Records a devnet chain's complete raw log range, plus the timestamp +//! of every block that carries a log, as a JSON fixture. Deliberately +//! raw and unfiltered: the tournament-state fold (workstream 5 of +//! docs/plans/node-refactor.md) decodes fixtures through the same +//! bindings the production fetcher uses, so a recording cannot bake in +//! decoding assumptions. Invoked by the e2e harness when +//! RECORD_CHAIN_FIXTURE is set (see prt/tests/rollups/test_env.lua). + +use anyhow::{Context, Result}; +use clap::Parser; +use std::collections::BTreeMap; +use std::path::PathBuf; + +use alloy::eips::BlockNumberOrTag; +use alloy::providers::{Provider, ProviderBuilder}; +use alloy::rpc::types::{Filter, Log}; + +#[derive(Parser)] +struct Args { + #[arg(long, default_value = "http://127.0.0.1:8545")] + rpc_url: String, + + /// Fixture destination (JSON). + #[arg(long)] + out: PathBuf, + + #[arg(long, default_value_t = 0)] + from_block: u64, + + /// Free-form context stored in the fixture (scenario name etc). + #[arg(long)] + note: Option, +} + +#[derive(serde::Serialize)] +struct Recording { + note: Option, + chain_id: u64, + from_block: u64, + to_block: u64, + /// Timestamps of every block carrying at least one log: the + /// clock-derivability lead needs them alongside the events. + block_timestamps: BTreeMap, + logs: Vec, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + let provider = ProviderBuilder::new().connect_http(args.rpc_url.parse()?); + + let chain_id = provider.get_chain_id().await?; + let to_block = provider.get_block_number().await?; + let filter = Filter::new().from_block(args.from_block).to_block(to_block); + let logs = provider.get_logs(&filter).await?; + + let mut block_timestamps = BTreeMap::new(); + for number in logs.iter().filter_map(|log| log.block_number) { + if let std::collections::btree_map::Entry::Vacant(entry) = block_timestamps.entry(number) { + let block = provider + .get_block_by_number(BlockNumberOrTag::Number(number)) + .await? + .with_context(|| format!("block {number} vanished mid-recording"))?; + entry.insert(block.header.timestamp); + } + } + + let recording = Recording { + note: args.note, + chain_id, + from_block: args.from_block, + to_block, + block_timestamps, + logs, + }; + if let Some(parent) = args.out.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(&args.out, serde_json::to_vec_pretty(&recording)?)?; + eprintln!( + "recorded {} logs through block {} to {}", + recording.logs.len(), + to_block, + args.out.display() + ); + Ok(()) +} diff --git a/cartesi-rollups/node/blockchain-reader/src/lib.rs b/cartesi-rollups/node/src/blockchain_reader/mod.rs similarity index 60% rename from cartesi-rollups/node/blockchain-reader/src/lib.rs rename to cartesi-rollups/node/src/blockchain_reader/mod.rs index e86472bd7..7c6d416d8 100644 --- a/cartesi-rollups/node/blockchain-reader/src/lib.rs +++ b/cartesi-rollups/node/src/blockchain_reader/mod.rs @@ -1,37 +1,24 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -mod error; - -use crate::error::{ProviderErrors, Result}; +use anyhow::Result; +use crate::chain::Chain; +use crate::sync::ShutdownSignal; use alloy::{ - contract::{Error, Event}, - eips::BlockNumberOrTag::Finalized, hex::ToHexExt, primitives::{Address, U256}, providers::Provider, - rpc::types::{Log, Topic}, - sol_types::SolEvent, }; -use async_recursion::async_recursion; use cartesi_machine::types::Hash; use log::{debug, info, trace}; -use num_traits::cast::ToPrimitive; -use rollups_state_manager::sync::Watch; -use std::ops::ControlFlow; -use std::{ - fmt, - iter::Peekable, - marker::{Send, Sync}, - time::Duration, -}; +use std::{fmt, iter::Peekable, time::Duration}; +use crate::storage::{Epoch, Input, InputId, Storage}; use cartesi_dave_contracts::dave_consensus::DaveConsensus::{self, EpochSealed}; use cartesi_rollups_contracts::{ application::Application, input_box::InputBox::{self, InputAdded}, }; -use rollups_state_manager::{Epoch, Input, InputId, StateManager}; #[derive(Debug, Clone, Copy)] pub struct AddressBook { @@ -137,56 +124,55 @@ impl AddressBook { } } -pub struct BlockchainReader { - state_manager: SM, +pub struct BlockchainReader { + storage: Storage, address_book: AddressBook, - input_reader: EventReader, - epoch_reader: EventReader, sleep_duration: Duration, } -impl BlockchainReader { - pub fn new( - state_manager: SM, - address_book: AddressBook, - sleep_duration: Duration, - long_block_range_error_codes: Vec, - ) -> Self { +impl BlockchainReader { + pub fn new(storage: Storage, address_book: AddressBook, sleep_duration: Duration) -> Self { Self { - state_manager, + storage, address_book, - input_reader: EventReader::::new(long_block_range_error_codes.clone()), - epoch_reader: EventReader::::new(long_block_range_error_codes), sleep_duration, } } - pub async fn execution_loop(mut self, watch: Watch, provider: impl Provider) -> Result<()> { + pub async fn execution_loop(mut self, shutdown: ShutdownSignal, chain: Chain) -> Result<()> { loop { - let current_block = latest_finalized_block(&provider).await?; - let prev_block = self.state_manager.latest_processed_block()?; - - if current_block > prev_block { - self.advance(&provider, prev_block, current_block).await?; + // A failed tick is retried, not fatal: the tick is + // re-derived from finalized state, so a provider hiccup + // costs one polling interval. This worker used to die on + // the first transient error - the exact failure class the + // epoch manager's 2026-07-10 fix addressed. + if let Err(e) = self.tick(&chain).await { + log::warn!("blockchain read failed, retrying next tick: {e}"); } - if matches!(watch.wait(self.sleep_duration), ControlFlow::Break(_)) { - break Ok(()); + tokio::select! { biased; + _ = shutdown.requested() => break Ok(()), + _ = tokio::time::sleep(self.sleep_duration) => {} } } } - async fn advance( - &mut self, - provider: &impl Provider, - prev_block: u64, - current_block: u64, - ) -> Result<()> { + async fn tick(&mut self, chain: &Chain) -> Result<()> { + let current_block = chain.finalized_block_number().await?; + let prev_block = self.storage.latest_processed_block()?; + + if current_block > prev_block { + self.advance(chain, prev_block, current_block).await?; + } + Ok(()) + } + + async fn advance(&mut self, chain: &Chain, prev_block: u64, current_block: u64) -> Result<()> { let (inputs, epochs) = self - .collect_events(provider, prev_block, current_block) + .collect_events(chain, prev_block, current_block) .await?; - self.state_manager.insert_consensus_data( + self.storage.insert_consensus_data( current_block, inputs.iter().collect::>().into_iter(), epochs.iter().collect::>().into_iter(), @@ -197,16 +183,16 @@ impl BlockchainReader { async fn collect_events( &mut self, - provider: &impl Provider, + chain: &Chain, prev_block: u64, current_block: u64, ) -> Result<(Vec, Vec)> { // read sealed epochs from blockchain let sealed_epochs: Vec = self - .collect_sealed_epochs(provider, prev_block, current_block) + .collect_sealed_epochs(chain, prev_block, current_block) .await?; - let last_sealed_epoch_opt = self.state_manager.last_sealed_epoch()?; + let last_sealed_epoch_opt = self.storage.last_sealed_epoch()?; let mut merged_sealed_epochs = Vec::new(); if let Some(last_sealed_epoch) = last_sealed_epoch_opt { merged_sealed_epochs.push(last_sealed_epoch); @@ -219,43 +205,33 @@ impl BlockchainReader { // read inputs from blockchain let inputs = self - .collect_inputs( - provider, - prev_block, - current_block, - merged_sealed_epochs_iter, - ) + .collect_inputs(chain, prev_block, current_block, merged_sealed_epochs_iter) .await?; Ok((inputs, sealed_epochs)) } async fn collect_sealed_epochs( - &self, - provider: &impl Provider, + &mut self, + chain: &Chain, prev_block: u64, current_block: u64, ) -> Result> { - Ok(self - .epoch_reader - .next( - provider, + Ok(chain + .decoded_logs::( + self.address_book.consensus, None, - &self.address_book.consensus, - prev_block, + // blocks are inclusive on both ends + prev_block + 1, current_block, ) .await? .iter() .map(|(e, meta)| { let epoch = Epoch { - epoch_number: e - .epochNumber - .to_u64() + epoch_number: u64::try_from(e.epochNumber) .expect("fail to convert epoch number"), - input_index_boundary: e - .inputIndexUpperBound - .to_u64() + input_index_boundary: u64::try_from(e.inputIndexUpperBound) .expect("fail to convert epoch boundary"), root_tournament: e.tournament, block_created_number: meta.block_number.expect("block number should exist"), @@ -271,19 +247,18 @@ impl BlockchainReader { async fn collect_inputs( &mut self, - provider: &impl Provider, + chain: &Chain, prev_block: u64, current_block: u64, sealed_epochs_iter: impl Iterator, ) -> Result> { // read new inputs from blockchain - let input_events: Vec<_> = self - .input_reader - .next( - provider, + let input_events: Vec<_> = chain + .decoded_logs::( + self.address_book.input_box, Some(&self.address_book.app.into_word().into()), - &self.address_book.input_box, - prev_block, + // blocks are inclusive on both ends + prev_block + 1, current_block, ) .await? @@ -291,7 +266,7 @@ impl BlockchainReader { .map(|i| i.0) .collect(); - let last_input = self.state_manager.last_input()?; + let last_input = self.storage.last_input()?; let (mut next_input_index_in_epoch, mut last_input_epoch_number) = { match last_input { @@ -371,165 +346,24 @@ impl BlockchainReader { } } -pub struct EventReader { - long_block_range_error_codes: Vec, - __phantom: std::marker::PhantomData, -} - -impl EventReader { - pub fn new(long_block_range_error_codes: Vec) -> Self { - Self { - long_block_range_error_codes, - __phantom: std::marker::PhantomData, - } - } - - async fn next( - &self, - provider: &impl Provider, - topic1: Option<&Topic>, - read_from: &Address, - prev_finalized: u64, - current_finalized: u64, - ) -> std::result::Result, ProviderErrors> { - assert!(current_finalized > prev_finalized); - - let logs = get_events( - provider, - topic1, - read_from, - // blocks are inclusive on both ends - prev_finalized + 1, - current_finalized, - &self.long_block_range_error_codes, - ) - .await - .map_err(ProviderErrors)?; - - Ok(logs) - } -} - -// Below is a simplified version originated from https://github.com/cartesi/state-fold -// ParitionProvider will attempt to fetch events in smaller partition if the original request is too large -#[async_recursion] -async fn get_events( - provider: &impl Provider, - topic1: Option<&Topic>, - read_from: &Address, - start_block: u64, - end_block: u64, - long_block_range_error_codes: &Vec, -) -> std::result::Result, Vec> { - // TODO: partition log queries if range too large - let event: Event<_, _, _> = { - let mut e = Event::new_sol(provider, read_from) - .from_block(start_block) - .to_block(end_block) - .event(E::SIGNATURE); - - if let Some(t) = topic1 { - e = e.topic1(t.clone()); - } - - e - }; - - match event.query().await { - Ok(l) => Ok(l), - Err(e) => { - if should_retry_with_partition(&e, long_block_range_error_codes) { - let middle = { - let blocks = 1 + end_block - start_block; - let half = blocks / 2; - start_block + half - 1 - }; - - let first_res = get_events( - provider, - topic1, - read_from, - start_block, - middle, - long_block_range_error_codes, - ) - .await; - - let second_res = get_events( - provider, - topic1, - read_from, - middle + 1, - end_block, - long_block_range_error_codes, - ) - .await; - - match (first_res, second_res) { - (Ok(mut first), Ok(second)) => { - first.extend(second); - Ok(first) - } - - (Err(mut first), Err(second)) => { - first.extend(second); - Err(first) - } - - (Err(err), _) | (_, Err(err)) => Err(err), - } - } else { - Err(vec![e]) - } - } - } -} - -async fn latest_finalized_block( - provider: &impl Provider, -) -> std::result::Result { - let block_number = provider - .get_block(Finalized.into()) - .await - .map_err(|e| ProviderErrors(vec![Error::TransportError(e)]))? - .expect("block is empty") - .header - .number; - - Ok(block_number) -} - -fn should_retry_with_partition( - err: &impl std::error::Error, - long_block_range_error_codes: &Vec, -) -> bool { - for code in long_block_range_error_codes { - let s = format!("{:?}", err); - if s.contains(&code.to_string()) { - return true; - } - } - - false -} - #[cfg(test)] mod test_utils; #[cfg(test)] mod blockchain_reader_tests { - use std::{sync::Arc, thread}; + use std::thread; - use crate::*; + use super::*; + use crate::merkle::Digest; + use crate::storage::Storage; use alloy::{ network::Ethereum, primitives::Address, - providers::{DynProvider, ProviderBuilder}, + providers::ProviderBuilder, sol_types::{SolCall, SolValue}, }; use cartesi_dave_contracts::dave_consensus::DaveConsensus::{self, EpochSealed}; - use cartesi_dave_merkle::Digest; use cartesi_machine::{ Machine, config::{ @@ -541,7 +375,6 @@ mod blockchain_reader_tests { input_box::InputBox::{self, InputAdded}, inputs::Inputs::EvmAdvanceCall, }; - use rollups_state_manager::persistent_state_access::PersistentStateAccess; use tokio::time::{Duration, sleep}; @@ -549,22 +382,19 @@ mod blockchain_reader_tests { const INPUT_PAYLOAD: &str = "Hello!"; const INPUT_PAYLOAD2: &str = "Hello Two!"; - use crate::test_utils::*; + use super::test_utils::*; - fn create_provider(url: &str) -> DynProvider { + fn create_chain(url: &str) -> Chain { let url = url.parse().unwrap(); - ProviderBuilder::new().connect_http(url).erased() - } - - fn create_epoch_reader() -> EventReader { - EventReader::::new(Vec::new()) - } - - fn create_input_reader() -> EventReader { - EventReader::::new(Vec::new()) + Chain::new( + ProviderBuilder::new() + .connect_client(rpc_client_with_timeout(url)) + .erased(), + Vec::new(), + ) } - fn state_access() -> (tempfile::TempDir, PersistentStateAccess) { + fn state_access() -> (tempfile::TempDir, Storage) { let state_dir_ = tempfile::tempdir().unwrap(); let state_dir = state_dir_.path(); @@ -573,7 +403,7 @@ mod blockchain_reader_tests { &MachineConfig::new_with_ram(RAMConfig { length: 134217728, backing_store: cartesi_machine::config::machine::BackingStoreConfig { - data_filename: "../../../test/programs/linux.bin".into(), + data_filename: "../../test/programs/linux.bin".into(), ..Default::default() }, }), @@ -582,7 +412,7 @@ mod blockchain_reader_tests { .unwrap(); machine.store(&machine_path).unwrap(); - let acc = PersistentStateAccess::migrate(state_dir, &machine_path, 0).unwrap(); + let acc = Storage::migrate(state_dir, &machine_path, 0, Address::ZERO).unwrap(); (state_dir_, acc) } @@ -608,29 +438,24 @@ mod blockchain_reader_tests { async fn read_epochs_until_count( url: &str, consensus_address: &Address, - epoch_reader: &EventReader, count: usize, ) -> Result> { - let provider = create_provider(url); + let chain = create_chain(url); let mut read_epochs = Vec::new(); while read_epochs.len() != count { + // each poll mines one block, marching the sealing block + // toward the finalized tag + mine_blocks(chain.provider(), 1).await?; // latest finalized block must be greater than 0 - let latest_finalized_block = std::cmp::max(1, latest_finalized_block(&provider).await?); - - read_epochs = epoch_reader - .next( - &provider, - None, - consensus_address, - 0, - latest_finalized_block, - ) + let finalized = std::cmp::max(1, chain.finalized_block_number().await?); + + read_epochs = chain + .decoded_logs::(*consensus_address, None, 1, finalized) .await? .into_iter() .map(|x| x.0) .collect(); - // wait a few seconds for the input added block to be finalized - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(20)).await; } Ok(read_epochs) @@ -640,44 +465,47 @@ mod blockchain_reader_tests { url: &str, inputbox_address: &Address, application_address: &Address, - input_reader: &EventReader, count: usize, ) -> Result> { - let provider = create_provider(url); + let chain = create_chain(url); let mut read_inputs = Vec::new(); while read_inputs.len() != count { + // each poll mines one block, marching the input blocks + // toward the finalized tag + mine_blocks(chain.provider(), 1).await?; // latest finalized block must be greater than 0 - let latest_finalized_block = std::cmp::max(1, latest_finalized_block(&provider).await?); + let finalized = std::cmp::max(1, chain.finalized_block_number().await?); - read_inputs = input_reader - .next( - &provider, + read_inputs = chain + .decoded_logs::( + *inputbox_address, Some(&application_address.into_word().into()), - inputbox_address, - 0, - latest_finalized_block, + 1, + finalized, ) .await? .into_iter() .map(|x| x.0) .collect(); - // wait a few seconds for the input added block to be finalized - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(20)).await; } Ok(read_inputs) } - async fn read_inputs_from_db_until_count( - state_manager: &mut SM, + async fn read_inputs_from_db_until_count( + provider: &impl Provider, + storage: &mut Storage, epoch_number: u64, count: usize, ) -> Result>> { let mut read_inputs = Vec::new(); while read_inputs.len() != count { - read_inputs = state_manager.inputs(epoch_number)?; - // wait a few seconds for the db to be updated - sleep(Duration::from_secs(1)).await; + // each poll mines one block so the reader thread sees the + // input blocks reach the finalized tag + mine_blocks(provider, 1).await?; + read_inputs = storage.inputs(epoch_number)?; + sleep(Duration::from_millis(20)).await; } Ok(read_inputs) @@ -692,12 +520,10 @@ mod blockchain_reader_tests { // Inputbox is deployed with 1 input already add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_1).await?; - let input_reader = create_input_reader(); let mut read_inputs = read_inputs_until_count( &anvil.endpoint(), inputbox.address(), &address_book.app, - &input_reader, 1 + input_count_1, ) .await?; @@ -713,7 +539,6 @@ mod blockchain_reader_tests { &anvil.endpoint(), inputbox.address(), &address_book.app, - &input_reader, 1 + input_count_1 + input_count_2, ) .await?; @@ -732,10 +557,8 @@ mod blockchain_reader_tests { let (anvil, provider, address_book) = spawn_anvil_and_provider().await?; let daveconsensus = DaveConsensus::new(address_book.consensus, &provider); - let epoch_reader = create_epoch_reader(); let read_epochs = - read_epochs_until_count(&anvil.endpoint(), daveconsensus.address(), &epoch_reader, 1) - .await?; + read_epochs_until_count(&anvil.endpoint(), daveconsensus.address(), 1).await?; assert_eq!(read_epochs.len(), 1); assert_eq!( &read_epochs[0].initialMachineStateHash.abi_encode(), @@ -754,7 +577,7 @@ mod blockchain_reader_tests { let inputbox = InputBox::new(address_book.input_box, provider.clone()); - let (handle, mut state_manager) = state_access(); + let (handle, mut storage) = state_access(); let input_count_0 = 1; @@ -763,15 +586,15 @@ mod blockchain_reader_tests { let input_count_1 = 2; add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_1).await?; - let watch = Watch::default(); + let shutdown = crate::sync::ShutdownSignal::default(); - let watch_0 = watch.clone(); + let shutdown_0 = shutdown.clone(); + let reader_chain = Chain::new(provider.clone(), Vec::new()); let r = thread::spawn(move || { let blockchain_reader = BlockchainReader::new( - PersistentStateAccess::new(handle.path()).unwrap(), + Storage::new(handle.path()).unwrap(), address_book, - Duration::from_secs(1), - Vec::new(), + Duration::from_millis(20), ); let rt = tokio::runtime::Builder::new_current_thread() @@ -781,21 +604,22 @@ mod blockchain_reader_tests { rt.block_on(async move { blockchain_reader - .execution_loop(watch_0, provider) + .execution_loop(shutdown_0, reader_chain) .await .unwrap(); }) }); - read_inputs_from_db_until_count(&mut state_manager, 0, 0).await?; - read_inputs_from_db_until_count(&mut state_manager, 1, input_count_0 + input_count_1) + read_inputs_from_db_until_count(&provider, &mut storage, 0, 0).await?; + read_inputs_from_db_until_count(&provider, &mut storage, 1, input_count_0 + input_count_1) .await?; // add inputs to epoch 1 let input_count_2 = 3; add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_2).await?; read_inputs_from_db_until_count( - &mut state_manager, + &provider, + &mut storage, 1, input_count_0 + input_count_1 + input_count_2, ) @@ -805,33 +629,17 @@ mod blockchain_reader_tests { let input_count_3 = 3; add_input(&inputbox, address_book.app, INPUT_PAYLOAD, input_count_3).await?; read_inputs_from_db_until_count( - &mut state_manager, + &provider, + &mut storage, 1, input_count_0 + input_count_1 + input_count_2 + input_count_3, ) .await?; - watch.notify(Arc::new(anyhow::anyhow!("".to_owned()))); + shutdown.request(); r.join().unwrap(); drop(anvil); Ok(()) } - - #[tokio::test] - async fn test_should_retry() -> Result<()> { - let s = r###"Error: HTTP error 400 with body: {"jsonrpc":"2.0","id":3,"error":{"code":-32600,"message":"You can make eth_getLogs requests with up to a 10000 block range. Based on your parameters, this block range should work: [0x1754746, 0x1756e55]"}}"###; - - assert!(should_retry_with_partition( - &std::io::Error::other(s), - &vec![ - "-32005".to_string(), - "-32600".to_string(), - "-32602".to_string(), - "-32616".to_string() - ] - )); - - Ok(()) - } } diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/src/blockchain_reader/test_utils.rs similarity index 73% rename from cartesi-rollups/node/blockchain-reader/src/test_utils.rs rename to cartesi-rollups/node/src/blockchain_reader/test_utils.rs index 64568153d..0535956bf 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/src/blockchain_reader/test_utils.rs @@ -1,4 +1,4 @@ -use crate::AddressBook; +use super::AddressBook; use alloy::{ hex::FromHex, network::EthereumWallet, @@ -7,7 +7,9 @@ use alloy::{ primitives::FixedBytes, primitives::U256, providers::{DynProvider, Provider, ProviderBuilder}, + rpc::client::RpcClient, signers::{Signer, local::PrivateKeySigner}, + transports::http::Http, }; use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory::{self, WithdrawalConfig}; use cartesi_machine::{Machine, config::runtime::RuntimeConfig}; @@ -17,9 +19,9 @@ use std::{fs, path::PathBuf}; type Result = std::result::Result>; -const PROGRAM: &str = "../../../test/programs/echo/"; -const ANVIL_STATE: &str = "../../../cartesi-rollups/contracts/state.json"; -const DEPLOYMENTS: &str = "../../../cartesi-rollups/contracts/deployments/31337"; +const PROGRAM: &str = "../../test/programs/echo/"; +const ANVIL_STATE: &str = "../../cartesi-rollups/contracts/state.json"; +const DEPLOYMENTS: &str = "../../cartesi-rollups/contracts/deployments/31337"; #[derive(Deserialize)] struct Deployment { @@ -45,11 +47,33 @@ pub fn deployment_address(contract_id: &str) -> Address { Address::from_hex(deployment.address).unwrap() } +/// A per-request timeout on every test provider: a wedged anvil must +/// fail a test loudly, not hang it forever (observed once under a +/// full disk, 2026-07-11; the production provider carries its own +/// timeout in provider.rs). +pub fn rpc_client_with_timeout(url: alloy::transports::http::reqwest::Url) -> RpcClient { + let http = alloy::transports::http::reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(10)) + .build() + .expect("failed to build reqwest client"); + RpcClient::builder().transport(Http::with_client(http, url), true) +} + +/// Mines `count` blocks on demand. The tests run anvil in automine +/// (a block per transaction, instantly); finality only advances with +/// new blocks, so waits for the finalized tag mine explicitly instead +/// of burning wall-clock seconds on interval mining. +pub async fn mine_blocks(provider: &impl Provider, count: u64) -> Result<()> { + provider + .raw_request::<_, serde_json::Value>("anvil_mine".into(), (count,)) + .await?; + Ok(()) +} + pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, AddressBook)> { let program_path = program_path(); let anvil = Anvil::default() - .block_time(1) .args([ "--preserve-historical-states", "--slots-in-an-epoch", @@ -69,8 +93,13 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let provider = ProviderBuilder::new() .wallet(wallet) - .connect_http(anvil.endpoint_url()) + .connect_client(rpc_client_with_timeout(anvil.endpoint_url())) .erased(); + // Automine confirms instantly; the default 250ms receipt poll + // would dominate every `.watch()`. + provider + .client() + .set_poll_interval(std::time::Duration::from_millis(10)); let input_box = deployment_address("InputBox"); let dave_app_factory = deployment_address("DaveAppFactory"); @@ -119,8 +148,7 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A .call() .await .expect("failed to calculate Dave app addresses") - .try_into() - .unwrap(); + .into(); dave_app_factory_contract .newDaveApp( diff --git a/cartesi-rollups/node/src/chain.rs b/cartesi-rollups/node/src/chain.rs new file mode 100644 index 000000000..467547c7e --- /dev/null +++ b/cartesi-rollups/node/src/chain.rs @@ -0,0 +1,157 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The chain facade: one home for the node's read-side provider +//! policy. Ranged log fetching with bisection-on-too-large (descended +//! from https://github.com/cartesi/state-fold), the provider-specific +//! long-range error codes that trigger it, and Latest/Finalized head +//! sampling live here, so workers stop threading provider quirks +//! through their constructors. + +use alloy::{ + eips::BlockNumberOrTag::{Finalized, Latest}, + primitives::Address, + providers::{DynProvider, Provider}, + rpc::types::{Filter, Log, Topic}, + sol_types::SolEvent, + transports::TransportError, +}; +use anyhow::{Result, anyhow}; + +#[derive(Debug, Clone)] +pub struct Chain { + provider: DynProvider, + long_block_range_error_codes: Vec, +} + +impl Chain { + pub fn new(provider: DynProvider, long_block_range_error_codes: Vec) -> Self { + Self { + provider, + long_block_range_error_codes, + } + } + + /// The underlying provider, for contract instances and pinned + /// point reads; policy-bearing access goes through the methods + /// below. + pub fn provider(&self) -> &DynProvider { + &self.provider + } + + pub async fn latest_block_number(&self) -> Result { + Ok(self + .provider + .get_block(Latest.into()) + .await? + .ok_or_else(|| anyhow!("provider has no latest block"))? + .header + .number) + } + + pub async fn finalized_block_number(&self) -> Result { + Ok(self + .provider + .get_block(Finalized.into()) + .await? + .ok_or_else(|| anyhow!("provider has no finalized block"))? + .header + .number) + } + + /// Every log emitted by `address` in `[from, to]`, in chain order. + pub async fn raw_logs(&self, address: Address, from: u64, to: u64) -> Result> { + let filter = Filter::new().address(address); + self.logs_bisecting(&filter, from, to).await + } + + /// `E`-typed logs emitted by `address` in `[from, to]`, optionally + /// narrowed by `topic1`, decoded and in chain order. + pub async fn decoded_logs( + &self, + address: Address, + topic1: Option<&Topic>, + from: u64, + to: u64, + ) -> Result> { + let mut filter = Filter::new().address(address).event(E::SIGNATURE); + if let Some(topic) = topic1 { + filter = filter.topic1(topic.clone()); + } + + self.logs_bisecting(&filter, from, to) + .await? + .into_iter() + .map(|log| { + let decoded = E::decode_log(&log.inner)?; + Ok((decoded.data, log)) + }) + .collect() + } + + /// Fetches `filter` over `[from, to]`, splitting the range in two + /// whenever the provider rejects it as too large (gateways cap + /// get_logs spans; the rejection surfaces as one of the configured + /// error codes). Iterative worklist, left half first, so logs come + /// back in ascending block order. + async fn logs_bisecting(&self, filter: &Filter, from: u64, to: u64) -> Result> { + let mut pending = vec![(from, to)]; + let mut logs = Vec::new(); + let mut errors: Vec = Vec::new(); + + while let Some((start, end)) = pending.pop() { + let ranged = filter.clone().from_block(start).to_block(end); + match self.provider.get_logs(&ranged).await { + Ok(batch) => logs.extend(batch), + Err(e) if start < end && self.is_long_range_rejection(&e) => { + let middle = start + (1 + end - start) / 2 - 1; + // LIFO: push the right half first so the left half + // is fetched first, preserving chain order. + pending.push((middle + 1, end)); + pending.push((start, middle)); + } + Err(e) => errors.push(e), + } + } + + if errors.is_empty() { + Ok(logs) + } else { + Err(anyhow!("get_logs failed: {errors:?}")) + } + } + + fn is_long_range_rejection(&self, err: &TransportError) -> bool { + matches_any_code(&self.long_block_range_error_codes, err) + } +} + +/// Substring match against the error's Debug rendering: provider +/// error shapes vary too much for structured matching, and the codes +/// are operator-supplied configuration. +fn matches_any_code(codes: &[String], err: &impl std::fmt::Debug) -> bool { + let rendered = format!("{:?}", err); + codes.iter().any(|code| rendered.contains(code)) +} + +#[cfg(test)] +mod tests { + use super::matches_any_code; + + #[test] + fn long_range_rejection_matches_by_error_code() { + let s = r###"Error: HTTP error 400 with body: {"jsonrpc":"2.0","id":3,"error":{"code":-32600,"message":"You can make eth_getLogs requests with up to a 10000 block range. Based on your parameters, this block range should work: [0x1754746, 0x1756e55]"}}"###; + + let codes: Vec = ["-32005", "-32600", "-32602", "-32616"] + .iter() + .map(|s| s.to_string()) + .collect(); + + assert!(matches_any_code(&codes, &std::io::Error::other(s))); + assert!(!matches_any_code( + &codes, + &std::io::Error::other("no code here") + )); + assert!(!matches_any_code(&[], &std::io::Error::other(s))); + } +} diff --git a/cartesi-rollups/node/src/engine/cache.rs b/cartesi-rollups/node/src/engine/cache.rs new file mode 100644 index 000000000..2893ec775 --- /dev/null +++ b/cartesi-rollups/node/src/engine/cache.rs @@ -0,0 +1,108 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The quartet compute engine over the storage-backed cache. +//! +//! `get_or_compute` is the one entry point disputes need for tree +//! material: commitment roots, bisection children, and proof siblings +//! are all just quartets. On a miss it computes the node's whole span +//! once and stores the subtree `PRECOMPUTE_LEVELS` deep, so descents +//! re-execute machine work only every `PRECOMPUTE_LEVELS` levels; the +//! total machine cost of a full descent is bounded by span * 1/(1 - 2^-8). +//! +//! The rows and their integrity semantics live behind [`Storage`] +//! (write-once positional keys, collision tripwire); this module owns +//! only what to compute and when. + +use super::ruler::RulerFactory; +use super::structure::{Quartet, Structure}; +use crate::merkle::{Digest, MerkleBuilder, MerkleTree}; +use crate::storage::Storage; +use anyhow::{Result, ensure}; +use std::sync::Arc; + +/// Fanout depth stored per miss: 2^0 + ... + 2^8 = 511 rows. Tunable; +/// storage is negligible next to the machine time a miss costs. +pub const PRECOMPUTE_LEVELS: u64 = 8; + +/// The engine of disputes: the hash of any quartet, computed at most +/// once per fanout stratum. +pub(crate) fn get_or_compute( + storage: &mut Storage, + structure: &Structure, + factory: &mut F, + quartet: &Quartet, +) -> Result { + quartet.assert_valid(structure); + if let Some(hash) = storage.quartet_node(quartet)? { + return Ok(hash); + } + compute_and_store(storage, structure, factory, quartet) +} + +/// The miss path: one span execution, fanout stored, regardless of +/// whether the root row already exists. Callers use it directly to +/// materialize a cached node's descendants (a proof descent crossing a +/// fanout stratum); the insert then doubles as a nondeterminism probe, +/// since a recomputed root that disagrees with its row fails loudly. +pub(crate) fn compute_and_store( + storage: &mut Storage, + structure: &Structure, + factory: &mut F, + quartet: &Quartet, +) -> Result { + quartet.assert_valid(structure); + + // Also a stable log marker the test harness kills on (see + // docs/test-harness.md); level-0 queries are seed-served, so this + // line means dispute-time machine work. + log::info!( + "computing quartet stride 2^{} height {} shift {} of epoch {}", + quartet.log2_stride, + quartet.height, + quartet.shift, + quartet.epoch + ); + + let mut ruler = factory.ruler_at(quartet.span_start())?; + let runs = ruler.collect(quartet.span_end(), quartet.log2_stride)?; + + let mut builder = MerkleBuilder::default(); + for run in &runs { + builder.append_repeated(run.hash, run.repetitions); + } + let tree = builder.build(); + ensure!( + u64::from(tree.height()) == quartet.height, + "span tree height {} does not match quartet height {}", + tree.height(), + quartet.height + ); + + let mut rows = vec![]; + collect_fanout( + &tree, + quartet, + PRECOMPUTE_LEVELS.min(quartet.height), + &mut rows, + ); + storage.insert_quartet_nodes(&rows)?; + + Ok(tree.root_hash()) +} + +fn collect_fanout( + node: &Arc, + quartet: &Quartet, + depth_left: u64, + rows: &mut Vec<(Quartet, Digest)>, +) { + rows.push((quartet.clone(), node.root_hash())); + if depth_left == 0 { + return; + } + let (left_q, right_q) = quartet.children().expect("depth bounded by height"); + let (left_t, right_t) = node.subtrees().expect("non-leaf by height"); + collect_fanout(&left_t, &left_q, depth_left - 1, rows); + collect_fanout(&right_t, &right_q, depth_left - 1, rows); +} diff --git a/cartesi-rollups/node/src/engine/config.rs b/cartesi-rollups/node/src/engine/config.rs new file mode 100644 index 000000000..b2a77dd14 --- /dev/null +++ b/cartesi-rollups/node/src/engine/config.rs @@ -0,0 +1,138 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The write-once configuration: everything contextual the cache rows +//! deliberately do not carry. +//! +//! This is migration-time state: the node's migration owns the DDL +//! (storage/sql/migrations.sql) and `pin` writes the row exactly once +//! at database creation; the dispute module only reads and asserts +//! (`assert_compatible`). + +use super::structure::Structure; +use crate::merkle::Digest; +use anyhow::{Result, ensure}; +use rusqlite::{Connection, OptionalExtension, params}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EngineConfig { + pub structure: Structure, + pub app: Vec, + pub template_hash: Digest, + pub emulator_version: String, +} + +/// Pins the configuration, once per database; the schema comes from +/// the node migration. Idempotent for an identical configuration; any +/// drift is refused. +pub fn pin(connection: &Connection, config: &EngineConfig) -> Result<()> { + config.structure.assert_valid(); + + match stored(connection)? { + Some(existing) => ensure!( + existing == *config, + "engine database configuration mismatch: stored {:?}, given {:?}", + existing, + config + ), + None => { + connection.execute( + "INSERT INTO sling_config VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6)", + params![ + config.structure.log2_input_span, + config.structure.log2_barch_span, + config.structure.log2_uarch_span, + config.app, + config.template_hash.slice(), + config.emulator_version, + ], + )?; + } + } + Ok(()) +} + +/// The dispute module's startup check: the stored pins must match the +/// running engine. Structure and emulator version only - the app and +/// template-hash pins are node-level facts the dispute side cannot +/// derive independently (the epoch snapshot hash differs from the +/// template hash past epoch zero). +pub fn assert_compatible( + stored: &EngineConfig, + structure: &Structure, + emulator_version: &str, +) -> Result<()> { + ensure!( + stored.structure == *structure, + "engine structure mismatch: stored {:?}, running {:?}", + stored.structure, + structure + ); + ensure!( + stored.emulator_version == emulator_version, + "emulator version drift: database pinned {}, running {}", + stored.emulator_version, + emulator_version + ); + Ok(()) +} + +/// The pinned configuration, if the database has one. +pub fn stored(connection: &Connection) -> Result> { + let config = connection + .query_row( + "SELECT log2_input_span, log2_barch_span, log2_uarch_span, + app, template_hash, emulator_version + FROM sling_config WHERE id = 0", + [], + |row| { + Ok(EngineConfig { + structure: Structure { + log2_input_span: row.get(0)?, + log2_barch_span: row.get(1)?, + log2_uarch_span: row.get(2)?, + }, + app: row.get(3)?, + template_hash: Digest::from_digest(&row.get::<_, Vec>(4)?) + .expect("stored hashes are 32 bytes"), + emulator_version: row.get(5)?, + }) + }, + ) + .optional()?; + Ok(config) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn config_is_write_once() -> Result<()> { + let dir = tempfile::tempdir()?; + let path = dir.path().join("cache.db"); + crate::storage::sql::migrations::migrate_to_latest(&mut Connection::open(&path)?)?; + let structure = Structure { + log2_input_span: 1, + log2_barch_span: 1, + log2_uarch_span: 2, + }; + let config = EngineConfig { + structure, + app: vec![0xaa; 20], + template_hash: Digest::from_digest(&[1u8; 32])?, + emulator_version: "0.20.0".into(), + }; + pin(&Connection::open(&path)?, &config)?; + + // Same config pins again fine (idempotent). + pin(&Connection::open(&path)?, &config)?; + assert_eq!(stored(&Connection::open(&path)?)?, Some(config.clone())); + + // Any drift is refused. + let mut drifted = config.clone(); + drifted.emulator_version = "0.21.0".into(); + assert!(pin(&Connection::open(&path)?, &drifted).is_err()); + Ok(()) + } +} diff --git a/prt/client-rs/core/src/machine/constants.rs b/cartesi-rollups/node/src/engine/constants.rs similarity index 51% rename from prt/client-rs/core/src/machine/constants.rs rename to cartesi-rollups/node/src/engine/constants.rs index 6bc96821e..d74be7428 100644 --- a/prt/client-rs/core/src/machine/constants.rs +++ b/cartesi-rollups/node/src/engine/constants.rs @@ -1,4 +1,12 @@ -use cartesi_dave_arithmetic as arithmetic; +//! The meta-cycle span constants shared with the contracts: the one +//! numeric authority for the production machine's shape. Everything +//! else derives from these - [`super::Structure::PRODUCTION`] and the +//! stride constants in `storage/rollups_machine.rs` - so a span change +//! happens here and nowhere else. Despite the `*_SPAN_*` names, the +//! non-LOG2 values are masks (2^n - 1), not spans; see +//! docs/glossary.md. + +use crate::arithmetic; // log2 value of the maximal number of micro instructions that emulates a big instruction pub const LOG2_UARCH_SPAN_TO_BARCH: u64 = 20; @@ -45,7 +53,7 @@ mod tests { fn test_emulator_and_step_agree_on_revert_address() { let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); let emulator_constants_sol = manifest_dir - .join("../../..") + .join("../..") .join("machine/step/src/EmulatorConstants.sol"); let source = std::fs::read_to_string(&emulator_constants_sol) .unwrap_or_else(|e| panic!("failed to read {}: {e}", emulator_constants_sol.display())); @@ -74,4 +82,75 @@ mod tests { The off-chain client and on-chain verifier will disagree on the revert slot." ); } + + /// The first number appearing after `marker` in `source` (digits + /// only, delimiters skipped): dumb but loud, like the parser above. + fn first_number_after(source: &str, marker: &str) -> u64 { + let pos = source + .find(marker) + .unwrap_or_else(|| panic!("{marker} not found in contract source")); + let after = &source[pos + marker.len()..]; + let start = after + .find(|c: char| c.is_ascii_digit()) + .expect("no number after marker"); + let digits: String = after[start..] + .chars() + .take_while(|c| c.is_ascii_digit()) + .collect(); + digits.parse().expect("digits parse as u64") + } + + /// Guardrail: the node's run stride and meta-cycle field widths + /// are hand-maintained mirrors of the arbitration contracts. A + /// drift would make the frontier fold serve level-0 nodes at a + /// stride the deployed tournament does not use - wrongness with + /// no loud error, since the fold bypasses the machine-replay + /// collision checks. (The tournament heights and deeper strides + /// are read live from chain; only these mirrors are static.) + #[test] + fn node_constants_match_arbitration_contracts() { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let root = manifest_dir.join("../.."); + + let arbitration = std::fs::read_to_string( + root.join("prt/contracts/src/arbitration-config/ArbitrationConstants.sol"), + ) + .expect("read ArbitrationConstants.sol"); + // log2step's array literal: the first element is level 0. + let log2step_fn = arbitration + .find("function log2step") + .expect("log2step in ArbitrationConstants.sol"); + let log2step_0 = first_number_after(&arbitration[log2step_fn..], "[uint64("); + assert_eq!( + crate::storage::rollups_machine::LOG2_STRIDE, + log2step_0, + "rollups LOG2_STRIDE does not match ArbitrationConstants.log2step(0)" + ); + let height_fn = arbitration + .find("function height") + .expect("height in ArbitrationConstants.sol"); + let height_0 = first_number_after(&arbitration[height_fn..], "[uint64("); + assert_eq!( + super::LOG2_INPUT_SPAN_TO_EPOCH + + super::LOG2_BARCH_SPAN_TO_INPUT + + super::LOG2_UARCH_SPAN_TO_BARCH, + log2step_0 + height_0, + "the ruler span does not match the root tournament's span" + ); + + let transition = std::fs::read_to_string( + root.join("prt/contracts/src/state-transition/CartesiStateTransition.sol"), + ) + .expect("read CartesiStateTransition.sol"); + assert_eq!( + super::LOG2_UARCH_SPAN_TO_BARCH, + first_number_after(&transition, "LOG2_UARCH_SPAN_TO_BARCH ="), + "uarch span width disagrees with CartesiStateTransition.sol" + ); + assert_eq!( + super::LOG2_BARCH_SPAN_TO_INPUT, + first_number_after(&transition, "LOG2_BARCH_SPAN_TO_INPUT ="), + "barch span width disagrees with CartesiStateTransition.sol" + ); + } } diff --git a/cartesi-rollups/node/src/engine/dispute.rs b/cartesi-rollups/node/src/engine/dispute.rs new file mode 100644 index 000000000..74af0927f --- /dev/null +++ b/cartesi-rollups/node/src/engine/dispute.rs @@ -0,0 +1,433 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The dispute-facing node source: every merkle node a tournament +//! hero needs, answered by quartet. +//! +//! A tournament level's commitment tree lives at a [`LevelCoords`]: +//! its root, the node a match contests, bisection children, and +//! leaf-proof siblings are all quartets under it. The source serves +//! them from three places, in order: the level-0 tiers (the leaf +//! runs regime 1 recorded while the epoch was open, served through +//! the persisted window-root rows plus lazy interior folds), the +//! quartet cache, and machine execution through +//! [`compute_and_store`]'s fanout. +//! +//! Proofs are descents: `prove_leaf` walks root to leaf collecting the +//! off-path sibling at each height through `children`, which +//! recomputes a parent's whole span when its children are missing - +//! one ruler pass per fanout stratum, and the recomputed parent must +//! agree with its cached row (a nondeterminism probe on every cold +//! descent). + +use super::cache::{compute_and_store, get_or_compute}; +use super::ruler::RulerFactory; +use super::structure::{Quartet, Structure}; +use crate::merkle::{Digest, MerkleBuilder, MerkleProof, MerkleTree}; +use crate::storage::Storage; +use alloy::primitives::U256; +use anyhow::{Result, ensure}; +use std::sync::Arc; + +/// Where a tournament level's commitment tree sits on the ruler. The +/// tournament contract supplies the shape (log2_stride, height) and +/// the span start (base_cycle, a meta-cycle); levels always tile +/// exactly, so base_cycle is aligned to the level's full span. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LevelCoords { + pub epoch: u64, + pub base_cycle: U256, + pub log2_stride: u64, + pub height: u64, +} + +impl LevelCoords { + pub fn new(epoch: u64, base_cycle: U256, log2_stride: u64, height: u64) -> Self { + let span = U256::from(1) << (log2_stride + height); + assert!( + (base_cycle % span).is_zero(), + "level base {base_cycle} not aligned to its span 2^{}", + log2_stride + height + ); + LevelCoords { + epoch, + base_cycle, + log2_stride, + height, + } + } + + pub fn root(&self) -> Quartet { + self.node(self.height, U256::ZERO) + } + + /// The node at `height` whose span starts at the level-local leaf + /// offset `leaf_offset`. This is the contested-node map: a match at + /// currentHeight h with runningLeafPosition p contests node(h, p), + /// since p is the leftmost leaf the contested node covers. + pub fn node(&self, height: u64, leaf_offset: U256) -> Quartet { + assert!(height <= self.height, "node higher than the level root"); + assert!( + leaf_offset < (U256::from(1) << self.height), + "leaf offset outside the level" + ); + assert!( + (leaf_offset % (U256::from(1) << height)).is_zero(), + "leaf offset not aligned to the node span" + ); + Quartet { + epoch: self.epoch, + log2_stride: self.log2_stride, + height, + shift: (self.base_cycle >> (self.log2_stride + height)) + (leaf_offset >> height), + } + } +} + +/// Folds leaf runs into the subtree they tile: repetitions must sum +/// to exactly 2^log2_leaves. Run-compressed - cost is bounded by +/// distinct runs, not leaves. The one level-0 fold both regimes +/// share: the runner folds each closed window's runs into its root +/// row, the facade folds window interiors on demand, and the epoch +/// roll folds window roots into the settlement root. +pub fn fold_runs( + runs: impl IntoIterator, + log2_leaves: u64, +) -> Result> { + let mut builder = MerkleBuilder::default(); + let mut total = 0u128; + for (hash, repetitions) in runs { + ensure!(repetitions > 0, "empty leaf run"); + builder.append_repeated(hash, repetitions); + total += repetitions as u128; + } + ensure!( + total == 1u128 << log2_leaves, + "leaf runs must tile 2^{log2_leaves} leaves, got {total}" + ); + Ok(builder.build()) +} + +/// Positional lookup: walk `depth` levels down from the root, taking +/// the branch each `index` bit names (high bit first). +fn descend(tree: Arc, depth: u64, index: U256) -> Arc { + let mut node = tree; + for i in (0..depth).rev() { + let (left, right) = node.subtrees().expect("depth bounded by height"); + node = if ((index >> i) & U256::from(1)).is_zero() { + left + } else { + right + }; + } + node +} + +/// The frontier composition: the one thing level 0 keeps for itself +/// (one-engine.md section 6, as amended). The open regime leaves a +/// dense prefix of window-root rows; everything right of the +/// frontier is the fixed point repeated. Nodes at or above window +/// granularity have spans that cross the frontier, so neither rows +/// alone nor the machine (a whole-epoch replay) can serve them - +/// this fold can, and it is the ONLY level-0 exception. Everything +/// below window granularity rides the ordinary machine regime, +/// exactly like a nested tournament below its level root, priced by +/// the same one-window replay that level-1 entry already pays. +/// +/// Rows are strict: a recorded window's root row is prepaid by the +/// advance commit, and its absence is corruption or version drift, +/// never something to heal around. +struct Frontier { + /// Windows with recorded material: the closed epoch's input + /// count. Zero stands the fold down (an inputless epoch is all + /// fixed point; the machine serves it as idle arithmetic). + recorded: u64, + /// What padding leaves repeat: the epoch's final boundary hash. + padding: Digest, + /// The tree over all window roots, folded on first touch from + /// one range scan; O(recorded) resident (run-compressed). + top: Option>, +} + +/// One epoch's node source. The cache spans epochs; the level-0 +/// material and the factory (its inputs) do not, so neither does the +/// source. +pub struct DisputeSource { + storage: Storage, + structure: Structure, + factory: F, + epoch: u64, + /// The stride the level-0 window roots were recorded at + /// (production: the rollups LOG2_STRIDE). The frontier fold + /// serves quartets at or above window granularity on it; below + /// that is the machine's domain. + log2_run_stride: u64, + frontier: Frontier, +} + +impl DisputeSource { + pub fn new(mut storage: Storage, factory: F, epoch: u64, log2_run_stride: u64) -> Result { + let structure = storage.sling_config()?.structure; + assert!( + log2_run_stride >= structure.log2_uarch_span, + "run stride below a big cycle: idle padding would churn" + ); + assert!( + log2_run_stride <= structure.log2_window_span(), + "run stride wider than a window" + ); + + // The frontier stands on the open regime's actual material, + // read as the PREFIX of window-root rows (shift < inputs): + // the coordinate legitimately carries machine-bought rows + // beyond the prefix - a dispute descent through a padding + // window's root stores its fanout there - and counting those + // once bricked reconstruction after the hero's own join. A + // store the runner never processed (the engine harnesses; a + // freshly migrated node) has an empty prefix and the machine + // serves everything - the pre-frontier full-replay behavior. + // A nonzero prefix must match the closed epoch's input count + // exactly; anything else is corruption. The padding value is + // the final boundary hash - the row the gap GC always keeps. + let interior_height = structure.log2_window_span() - log2_run_stride; + let inputs = storage.input_count(epoch)?; + let rows = storage.window_root_count(epoch, log2_run_stride, interior_height, inputs)?; + // Invariant violations panic: the callers' tick loops retry + // Err forever, which would silently livelock the dispute on a + // corrupt store (the loudness doctrine, node-architecture.md). + let recorded = if rows == 0 { + 0 + } else { + assert_eq!( + rows, inputs, + "epoch {epoch} has {rows} window-root rows in the prefix of \ + {inputs} inputs: corruption or version drift" + ); + inputs + }; + let padding = if recorded > 0 { + let hash = storage.snapshot_hash(epoch, recorded)?.unwrap_or_else(|| { + panic!( + "final boundary row missing for epoch {epoch} at input {recorded}: \ + corruption or version drift" + ) + }); + Digest::from_digest(&hash)? + } else { + Digest::ZERO + }; + + Ok(DisputeSource { + storage, + structure, + factory, + epoch, + log2_run_stride, + frontier: Frontier { + recorded, + padding, + top: None, + }, + }) + } + + pub fn factory(&self) -> &F { + &self.factory + } + + /// A ruler positioned at `position`: the machine verb of the + /// facade. This is what proof positioning uses (the disputed + /// leaf's transition witness) and what entering a nested + /// tournament uses to start producing the nested computation + /// hash. Positioning resumes from the boundary store's nearest + /// answer and densifies as it advances. + pub fn machine_at(&mut self, position: U256) -> Result> { + self.factory.ruler_at(position) + } + + /// Frontier coverage: the quartet sits at or above window + /// granularity on the run stride, and the epoch recorded material + /// to serve it from. Below window granularity every quartet - + /// real or padding window alike - is the machine's domain, like + /// any nested level (a padding-window replay is one snapshot load + /// plus idle arithmetic). + /// + /// Coverage caveat (inherited from the SeedTree, unreachable + /// today): a covered height-0 quartet at a stride strictly above + /// the run stride names one sampled state, which is not the fold + /// this serves; the two agree only when the leaf stride equals + /// the run stride. No reachable geometry asks for one (production + /// level strides are 44/27/0 and levels never coarsen), but + /// revisit this dispatch if a level stride ever lands strictly + /// above the run stride. + fn covered(&self, quartet: &Quartet) -> bool { + assert_eq!(quartet.epoch, self.epoch, "quartet from another epoch"); + self.frontier.recorded > 0 + && quartet.log2_stride >= self.log2_run_stride + && quartet.height + (quartet.log2_stride - self.log2_run_stride) + >= self.interior_height() + } + + /// Leaves of one window's level-0 subtree: log2_window_span less + /// the run stride (production: height 24 over stride 44). + fn interior_height(&self) -> u64 { + self.structure.log2_window_span() - self.log2_run_stride + } + + /// The tree over all window roots, tiling the whole ruler: the + /// recorded prefix from its rows (one strict range scan), the + /// padding window root - the fixed point iterated up - repeated + /// to fill the input span. Memoized; folding is O(recorded). + fn top_tree(&mut self) -> Result> { + if let Some(tree) = &self.frontier.top { + return Ok(Arc::clone(tree)); + } + let interior_height = self.interior_height(); + let roots = self.storage.window_root_range( + self.epoch, + self.log2_run_stride, + interior_height, + self.frontier.recorded, + )?; + let mut runs: Vec<(Digest, u64)> = roots.into_iter().map(|root| (root, 1)).collect(); + let max_windows = self.structure.max_inputs(); + if self.frontier.recorded < max_windows { + let padding_root = fold_runs( + [(self.frontier.padding, 1u64 << interior_height)], + interior_height, + )? + .root_hash(); + runs.push((padding_root, max_windows - self.frontier.recorded)); + } + let tree = fold_runs(runs, self.structure.log2_input_span)?; + self.frontier.top = Some(Arc::clone(&tree)); + Ok(tree) + } + + /// A covered quartet's subtree: a positional walk down the top + /// tree. Covered quartets consume exactly their shift bits. + fn level0_subtree(&mut self, quartet: &Quartet) -> Result> { + debug_assert!(self.covered(quartet)); + let height_in_level0 = quartet.height + (quartet.log2_stride - self.log2_run_stride); + let depth = self.structure.log2_input_span - (height_in_level0 - self.interior_height()); + Ok(descend(self.top_tree()?, depth, quartet.shift)) + } + + /// The hash of any quartet. + pub fn node(&mut self, quartet: &Quartet) -> Result { + if self.covered(quartet) { + return Ok(self.level0_subtree(quartet)?.root_hash()); + } + get_or_compute( + &mut self.storage, + &self.structure, + &mut self.factory, + quartet, + ) + } + + /// Both children of a quartet: the bisection and proof primitive. + /// A missing child recomputes the parent's span, not the child's - + /// half the machine trips of computing each child separately, and + /// the parent row collision-checks the recomputation. + pub fn children(&mut self, parent: &Quartet) -> Result<(Digest, Digest)> { + let (left, right) = parent.children().expect("children of a leaf quartet"); + if self.covered(&left) { + // Both children (hence the parent) sit at or above window + // granularity: the frontier fold serves them. + return Ok((self.node(&left)?, self.node(&right)?)); + } + if let (Some(l), Some(r)) = ( + self.storage.quartet_node(&left)?, + self.storage.quartet_node(&right)?, + ) { + return Ok((l, r)); + } + // When the parent is a window root, this span recomputation + // collision-checks the open regime's persisted fold - the + // dispute path re-verifying level-0 material with the machine. + compute_and_store( + &mut self.storage, + &self.structure, + &mut self.factory, + parent, + )?; + let l = self + .storage + .quartet_node(&left)? + .expect("fanout stores the children"); + let r = self + .storage + .quartet_node(&right)? + .expect("fanout stores the children"); + Ok((l, r)) + } + + /// Merkle proof of a level leaf: the descent from the level root, + /// collecting the off-path sibling at each height. Siblings come + /// out bottom-up, matching the on-chain verifier's order. + pub fn prove_leaf(&mut self, level: &LevelCoords, index: U256) -> Result { + assert!( + index < (U256::from(1) << level.height), + "leaf index outside the level" + ); + let mut siblings = Vec::with_capacity(level.height as usize); + let mut node = level.root(); + let mut hash = self.node(&node)?; + while node.height > 0 { + let (left_hash, right_hash) = self.children(&node)?; + let (left, right) = node.children().expect("non-leaf by loop condition"); + let bit = (index >> (node.height - 1)) & U256::from(1); + (node, hash) = if bit.is_zero() { + siblings.push(right_hash); + (left, left_hash) + } else { + siblings.push(left_hash); + (right, right_hash) + }; + } + siblings.reverse(); + Ok(MerkleProof { + position: index, + node: hash, + siblings, + }) + } + + /// Proof of the level's last leaf, as join demands. + pub fn prove_last(&mut self, level: &LevelCoords) -> Result { + let last = (U256::from(1) << level.height) - U256::from(1); + self.prove_leaf(level, last) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn level_coordinates_by_hand() { + // Level of span 2^5 (stride 2, height 3) starting at ruler + // position 0x80: base leaf index 32, so the node of height 1 + // at leaf offset 6 covers leaves 38..40, i.e. shift 19. + let level = LevelCoords::new(3, U256::from(0x80), 2, 3); + let root = level.root(); + assert_eq!(root.epoch, 3); + assert_eq!(root.log2_stride, 2); + assert_eq!(root.height, 3); + assert_eq!(root.shift, U256::from(4)); + assert_eq!(root.span_start(), U256::from(0x80)); + assert_eq!(root.span_end(), U256::from(0xa0)); + + let contested = level.node(1, U256::from(6)); + assert_eq!(contested.height, 1); + assert_eq!(contested.shift, U256::from(19)); + assert_eq!(contested.span_start(), U256::from(0x80 + 6 * 4)); + } + + #[test] + #[should_panic(expected = "not aligned")] + fn level_base_must_tile() { + LevelCoords::new(0, U256::from(1), 2, 3); + } +} diff --git a/cartesi-rollups/node/src/engine/machine_stf.rs b/cartesi-rollups/node/src/engine/machine_stf.rs new file mode 100644 index 000000000..00bae4b6c --- /dev/null +++ b/cartesi-rollups/node/src/engine/machine_stf.rs @@ -0,0 +1,679 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The reference collector: the [`Stf`] verbs implemented on the real +//! Cartesi machine through the current (0.20) API, one step at a time. +//! +//! This is deliberately the slow, obviously-correct implementation. It +//! exists to be validated against the prototype's commitment builder +//! (the only oracle available today) and to serve, permanently, as the +//! differential reference for the fast bulk collectors that arrive with +//! emulator 0.21. Machine errors propagate as errors; geometry +//! violations remain panics (see the stf module doc). + +use super::dispute::DisputeSource; +use super::ruler::{Ruler, RulerFactory}; +use super::stf::{ProvingStf, Stf}; +use super::structure::Structure; +use crate::arithmetic::add_and_clamp; +use crate::engine::constants::CHECKPOINT_ADDRESS; +use crate::merkle::Digest; +use crate::storage::{InputId, Storage}; +use alloy::primitives::U256; +use anyhow::{Context, Result, ensure}; +use cartesi_machine::{ + cartesi_machine_sys, + config::runtime::RuntimeConfig, + constants::cmio::tohost::manual::{RX_ACCEPTED, RX_REJECTED, TX_EXCEPTION}, + constants::machine::HASH_TREE_LOG2_ROOT_SIZE, + format_emulator_version, + machine::Machine, + types::{LogType, access_proof::AccessLog, cmio::CmioResponseReason}, +}; +use std::path::{Path, PathBuf}; + +/// Where feed's payloads and its pre-feed snapshot live - both are +/// aspects of the one fused verb. Scratch carries explicit payload +/// vectors and per-ruler checkpoint dirs (the storage-less +/// harnesses: measure, the differential tests). Store is the dispute +/// path's mode: payloads come from the inputs table and the pre-feed +/// snapshot commits into the boundary store, where it doubles as +/// dispute densification and the row insert as a cross-regime +/// nondeterminism tripwire. Advance is the machine runner's mode: +/// one window, its payload handed in (the runner already read the +/// inputs table to schedule it), and the pre-feed snapshot IS the +/// batch's committed boundary directory - never a store; a revert +/// restores through the boundary store's own artifact. +enum Feeder { + Scratch { + fed: usize, + inputs: Vec>, + }, + Store { + storage: Storage, + epoch: u64, + next_input: u64, + }, + Advance { + window: u64, + payload: Option>, + boundary: PathBuf, + reverted: bool, + }, +} + +pub struct MachineStf { + machine: Machine, + /// Uarch cycles since the last reset; run_uarch takes absolutes. + ucycle: u64, + /// Scratch-mode checkpoints live here; one at a time. + work_dir: PathBuf, + checkpoint: Option, + feeder: Feeder, +} + +impl MachineStf { + /// Loads a template machine (the epoch's initial state). It must be + /// yielded awaiting the first input, with a pristine uarch. + pub fn load(template_path: &Path, work_dir: PathBuf) -> Result { + // resume already validates the pristine uarch + let mut stf = Self::resume(template_path, work_dir)?; + ensure!( + stf.machine.iflags_y()?, + "template machine must be yielded awaiting input" + ); + Ok(stf) + } + + /// Resumes a stored machine mid-epoch (a boundary-store answer). + /// Positions are big-cycle boundaries, so the uarch must be + /// pristine, but the machine may be in any big state. + pub fn resume(path: &Path, work_dir: PathBuf) -> Result { + let mut machine = Machine::load(path, &RuntimeConfig::quiet_console()) + .context("failed to load stored machine")?; + ensure!( + machine.ucycle()? == 0, + "stored machine must sit at a big-cycle boundary" + ); + std::fs::create_dir_all(&work_dir).context("work dir")?; + Ok(MachineStf { + machine, + ucycle: 0, + work_dir, + checkpoint: None, + feeder: Feeder::Scratch { + fed: 0, + inputs: vec![], + }, + }) + } + + /// Scratch-mode payloads for the windows this stf will feed + /// (index 0 is the first window fed from here). Panics in store + /// mode, which carries payloads from the inputs table. + pub fn with_inputs(mut self, payloads: Vec>) -> Self { + match &mut self.feeder { + Feeder::Scratch { inputs, .. } => *inputs = payloads, + _ => panic!("only the scratch feeder carries payload vectors"), + } + self + } + + /// The machine runner's stf: wraps the live working-clone machine + /// the caller owns (SHARING_ALL, mutated in place, sitting at + /// `window`'s boundary), feeds exactly that window, and restores + /// a revert from `boundary` - the batch's committed pre-input + /// directory. No filesystem effects of its own; the counterpart + /// of [`MachineStf::into_machine`]. + pub fn over_advancing( + machine: Machine, + window: u64, + payload: Vec, + boundary: PathBuf, + ) -> Self { + MachineStf { + machine, + ucycle: 0, + // Advance mode never writes scratch checkpoints; an empty + // path fails loudly if a bug ever routes there. + work_dir: PathBuf::new(), + checkpoint: None, + feeder: Feeder::Advance { + window, + payload: Some(payload), + boundary, + reverted: false, + }, + } + } + + /// Deconstructs into the wrapped machine: the working clone + /// (accepted window) or the boundary-restored instance (reverted + /// window), which the caller's record verbs swap out anyway. + pub fn into_machine(self) -> Machine { + self.machine + } + + /// Whether the last fed window reverted (advance mode only; the + /// dispute path replays reverts positionally and never asks). + pub fn took_revert(&self) -> bool { + matches!(self.feeder, Feeder::Advance { reverted: true, .. }) + } + + /// Upgrades the feeder into the node's storage: this machine sits + /// at `next_input`'s boundary of `epoch`; every window it feeds + /// from here reads its payload from the inputs table and commits + /// the crossed boundary. + pub fn with_write_back(mut self, storage: Storage, epoch: u64, next_input: u64) -> Self { + self.feeder = Feeder::Store { + storage, + epoch, + next_input, + }; + self + } + + /// Stores the machine; the counterpart of resume. + pub fn store(&mut self, path: &Path) -> Result<()> { + self.machine.store(path)?; + Ok(()) + } + + fn fixed(&mut self) -> Result { + Ok(self.halted()? || self.yielded()?) + } +} + +impl Stf for MachineStf { + fn state_hash(&mut self) -> Result { + Ok(self.machine.root_hash()?.into()) + } + + fn halted(&mut self) -> Result { + Ok(self.machine.iflags_h()?) + } + + fn yielded(&mut self) -> Result { + Ok(self.machine.iflags_y()?) + } + + fn uarch_halted(&mut self) -> Result { + Ok(self.machine.uarch_halt_flag()?) + } + + fn feed(&mut self, window: u64) -> Result<()> { + assert!(self.yielded()? && !self.halted()?, "feed requires yielded"); + + // Snapshot the pre-feed state: the off-chain form of the + // checkpoint the on-chain revert reads from the shadow slot. + // The snapshot predates the slot write below, matching what + // the on-chain revert restores (the pre-checkpoint root). + let root = self.machine.root_hash()?; + let (checkpoint, payload) = match &mut self.feeder { + Feeder::Scratch { fed, inputs } => { + assert_eq!( + window as usize, *fed, + "windows feed sequentially from the resume point" + ); + let path = self.work_dir.join(format!("checkpoint-{fed}")); + *fed += 1; + self.machine.store(&path).context("store checkpoint")?; + if let Some(old) = self.checkpoint.take() { + std::fs::remove_dir_all(old).ok(); + } + let payload = inputs + .get(window as usize) + .cloned() + .expect("scratch feeder must carry every fed payload"); + (path, payload) + } + Feeder::Store { + storage, + epoch, + next_input, + } => { + assert_eq!( + window, *next_input, + "windows feed sequentially from the resume point" + ); + *next_input += 1; + let payload = storage + .input(&InputId { + epoch_number: *epoch, + input_index_in_epoch: window, + })? + .expect("fed windows lie in the ingested contiguous prefix") + .data; + // The write-back: this boundary joins the store + // (stored only where regime 1 has not already), and + // the committed directory is the revert point - + // never removed here, it is the store's. + let dir = + storage.commit_boundary_machine(*epoch, window, &root, &mut self.machine)?; + (dir, payload) + } + Feeder::Advance { + window: expected, + payload, + boundary, + reverted, + } => { + assert_eq!( + window, *expected, + "the advance stf feeds exactly its window" + ); + *reverted = false; + let payload = payload.take().expect("the advance stf feeds once"); + // The pre-feed state is already committed: it is the + // boundary the working clone was checked out from. + (boundary.clone(), payload) + } + }; + self.checkpoint = Some(checkpoint); + + self.machine.write_memory(CHECKPOINT_ADDRESS, &root)?; + self.machine + .send_cmio_response(CmioResponseReason::Advance, &payload)?; + Ok(()) + } + + fn ustep(&mut self) -> Result<()> { + if self.uarch_halted()? { + return Ok(()); + } + self.machine.run_uarch(self.ucycle + 1)?; + self.ucycle += 1; + Ok(()) + } + + fn ureset(&mut self) -> Result<()> { + self.machine.reset_uarch()?; + self.ucycle = 0; + Ok(()) + } + + fn revert_if_needed(&mut self) -> Result { + if !self.yielded()? { + return Ok(false); + } + // The on-chain closing slot restores the checkpoint ONLY on + // RX_REJECTED (AdvanceStatus + CmioStateTransition + // .revertIfNeeded): an exception yield KEEPS the exception + // state, and any other manual reason has no defined + // transition on-chain (InvalidReason), so it is fatal here + // too. Solidity is the source of truth for these semantics; + // treating every non-accept as a revert was a consensus + // mismatch (found 2026-07-15). + let reason = self.machine.receive_cmio_request()?.reason(); + match reason { + RX_ACCEPTED | TX_EXCEPTION => Ok(false), + RX_REJECTED => { + let checkpoint = self + .checkpoint + .as_ref() + .expect("revert requires a fed checkpoint"); + // Replacing the instance drops the old machine + // (flushing and unlocking a shared working clone); + // the poisoned directory is the caller's to discard. + self.machine = Machine::load(checkpoint, &RuntimeConfig::quiet_console()) + .context("reload checkpoint")?; + self.ucycle = 0; + if let Feeder::Advance { reverted, .. } = &mut self.feeder { + *reverted = true; + } + Ok(true) + } + other => panic!( + "manual yield reason {other} has no defined state transition \ + (the on-chain advanceStatus rejects it)" + ), + } + } + + fn run_big(&mut self, big_cycles: u64) -> Result { + assert_eq!(self.ucycle, 0, "run_big requires a big-cycle boundary"); + if big_cycles == 0 || self.fixed()? { + return Ok(0); + } + let start = self.machine.mcycle()?; + let target = add_and_clamp(start, big_cycles); + loop { + self.machine.run(target)?; + if self.halted()? || self.yielded()? { + break; + } + if self.machine.mcycle()? == target { + break; + } + } + Ok(self.machine.mcycle()? - start) + } +} + +// The chain witness encoding, byte-compatible with what the on-chain +// state transition decodes (and with the prototype proof path it +// replaces; the differential test in tests/engine_machine.rs pins the +// bytes). +impl MachineStf { + fn prove_read_word(&mut self, address: u64) -> Result> { + // always read aligned 32 bytes (one leaf) + let aligned_address = address & !0x1Fu64; + let mut read = self.machine.read_memory(aligned_address, 32)?; + let proof = self + .machine + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; + + let mut encoded: Vec = Vec::new(); + encoded.append(&mut read); + let mut decoded_siblings: Vec = + proof.sibling_hashes.iter().flatten().cloned().collect(); + encoded.append(&mut decoded_siblings); + + Ok(encoded) + } + + fn prove_read_leaf(&mut self, address: u64) -> Result> { + // always read aligned 32 bytes (one leaf) + let aligned_address = address & !0x1Fu64; + let mut read = self.machine.read_memory(aligned_address, 32)?; + let read_hash = Digest::from_data(&read); + let proof = self + .machine + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; + + let mut encoded: Vec = Vec::new(); + encoded.append(&mut read); + encoded.append(&mut read_hash.slice().to_vec()); + let mut decoded_siblings: Vec = + proof.sibling_hashes.iter().flatten().cloned().collect(); + encoded.append(&mut decoded_siblings); + + Ok(encoded) + } + + /// Proves the pre-write leaf value, then performs the checkpoint + /// write (the current root hash into the shadow slot). + fn prove_write_checkpoint(&mut self) -> Result> { + let address = CHECKPOINT_ADDRESS; + assert!(address & 0x1F == 0); + let read = self.machine.read_memory(address, 32)?; + let read_hash = Digest::from_data(&read); + let proof = self.machine.proof(address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; + + let mut encoded: Vec = Vec::new(); + encoded.append(&mut read_hash.slice().to_vec()); + let mut decoded_siblings: Vec = + proof.sibling_hashes.iter().flatten().cloned().collect(); + encoded.append(&mut decoded_siblings); + + let checkpoint = self.state_hash()?; + self.machine.write_memory(address, checkpoint.slice())?; + + Ok(encoded) + } + + fn encode_access_log(log: &AccessLog) -> Vec { + let mut encoded: Vec> = Vec::new(); + + for a in log.accesses.iter() { + if a.log2_size == 3 { + encoded.push(a.read.clone().unwrap()); + } else { + encoded.push(a.read_hash.to_vec()); + } + + let decoded_siblings: Vec> = a + .sibling_hashes + .clone() + .unwrap() + .iter() + .map(|h| h.to_vec()) + .collect(); + encoded.extend_from_slice(&decoded_siblings); + } + + encoded.iter().flatten().cloned().collect() + } + + fn encode_da(input: &[u8]) -> Vec { + let input_size_be = (input.len() as u64).to_be_bytes().to_vec(); + let mut da_proof = input_size_be; + da_proof.extend_from_slice(input); + da_proof + } +} + +impl ProvingStf for MachineStf { + fn log_feed(&mut self, window: u64) -> Result> { + // The proving path resolves the payload without touching the + // feed cursor or the checkpoint: the machine is spent after + // the proof. + let payload = match &mut self.feeder { + Feeder::Scratch { inputs, .. } => inputs.get(window as usize).cloned(), + Feeder::Store { storage, epoch, .. } => storage + .input(&InputId { + epoch_number: *epoch, + input_index_in_epoch: window, + })? + .map(|input| input.data), + Feeder::Advance { .. } => { + unreachable!("the advance stf collects forward; proving rides the dispute path") + } + }; + match payload { + Some(input) => { + let checkpoint_proof = self.prove_write_checkpoint()?; + let cmio_log = self.machine.log_send_cmio_response( + CmioResponseReason::Advance, + &input, + LogType::default(), + )?; + Ok([ + Self::encode_da(&input), + checkpoint_proof, + Self::encode_access_log(&cmio_log), + ] + .concat()) + } + None => Ok(Self::encode_da(&[])), + } + } + + fn log_ustep(&mut self) -> Result> { + let log = self.machine.log_step_uarch(LogType::default())?; + self.ucycle += 1; + Ok(Self::encode_access_log(&log)) + } + + fn log_ureset(&mut self) -> Result> { + let log = self.machine.log_reset_uarch(LogType::default())?; + self.ucycle = 0; + Ok(Self::encode_access_log(&log)) + } + + fn log_revert_check(&mut self) -> Result> { + let mut proof = Vec::new(); + + let iflags_y_address = + cartesi_machine::Machine::reg_address(cartesi_machine_sys::CM_REG_IFLAGS_Y)?; + proof.append(&mut self.prove_read_word(iflags_y_address)?); + + if self.yielded()? { + let to_host_address = + cartesi_machine::Machine::reg_address(cartesi_machine_sys::CM_REG_HTIF_TOHOST)?; + proof.append(&mut self.prove_read_word(to_host_address)?); + + // The chain consumes the checkpoint leaf only on the + // REJECTED branch (getRevertRootHash); an exception yield + // keeps its state and reads nothing more. + if self.machine.receive_cmio_request()?.reason() == RX_REJECTED { + proof.append(&mut self.prove_read_leaf(CHECKPOINT_ADDRESS)?); + } + } + Ok(proof) + } +} + +/// The engine's positioning residue: a work dir, a spawn counter, +/// and the store handle they serve. Positions rulers by resuming +/// from the boundary store's nearest stored machine and advancing +/// the remainder. The store is live: boundaries recorded by any +/// writer (the open regime's gap fill, a future dispute write-back) +/// shorten the next positioning. On a freshly migrated store only +/// the epoch start exists, which is the full-replay behavior the +/// prototype had. Constructed only by [`DisputeSource::on_store`]; +/// the type is public for signatures alone. +pub struct Positioner { + structure: Structure, + work_dir: PathBuf, + /// How many windows feed (the inputs are a contiguous prefix); + /// payloads stay in the inputs table, read at feed time. + fed_windows: u64, + store: Storage, + epoch: u64, + spawned: usize, +} + +/// The production constructor of the facade: one closed epoch's +/// computation, assembled entirely from the node's durable state. +/// Lives beside the machine stf because only this impl block knows +/// how positioning constructs itself from storage; consumers hold no +/// engine pieces. +impl DisputeSource { + pub fn on_store(mut storage: Storage, epoch: u64, work_dir: PathBuf) -> Result { + // The migration pinned the config; assert engine + // compatibility before serving any quartet. + let structure = Structure::PRODUCTION; + super::config::assert_compatible( + &storage.sling_config()?, + &structure, + &format_emulator_version(Machine::version()), + )?; + + let fed_windows = storage.input_count(epoch)?; + + let positioner = Positioner { + structure, + work_dir, + fed_windows, + // Its own store handle: one connection per holder, like + // every Storage user. + store: Storage::new(storage.state_dir())?, + epoch, + spawned: 0, + }; + // The level-0 material was recorded at the rollups stride; + // the source reads it (window-root rows, interior runs) from + // storage on demand. + DisputeSource::new( + storage, + positioner, + epoch, + crate::storage::rollups_machine::LOG2_STRIDE, + ) + } +} + +impl RulerFactory for Positioner { + type S = MachineStf; + + fn ruler_at(&mut self, position: U256) -> Result> { + let mut target = self.structure.decompose(position).input; + let (boundary, stf) = loop { + let (boundary, path) = self + .store + .nearest_boundary_at_or_before(self.epoch, target)?; + + let dir = self.work_dir.join(format!("stf-{}", self.spawned)); + self.spawned += 1; + // A previous process may have left checkpoints here, and + // the machine refuses to store over an existing directory. + std::fs::remove_dir_all(&dir).ok(); + let mut stf = if boundary.0 == 0 { + MachineStf::load(&path, dir)? + } else { + MachineStf::resume(&path, dir)? + }; + + // Assert-on-load: the emulator validates nothing, so the + // loaded machine must reproduce its row's hash (nearly + // free - committed boundaries carry exact sidecars). A + // torn snapshot is skipped, not fatal: any earlier + // boundary only lengthens the replay. + let expected = self + .store + .snapshot_hash(self.epoch, boundary.0)? + .expect("nearest answered from an existing row"); + if stf.state_hash()? == Digest::from_digest(&expected)? { + break (boundary, stf); + } + log::error!( + "stored boundary {} of epoch {} does not hash to its row: \ + torn snapshot? skipping it", + boundary.0, + self.epoch + ); + ensure!(boundary.0 > 0, "the epoch start snapshot is corrupt"); + target = boundary.0 - 1; + }; + + // The seam's one boundary-to-position conversion. + let at = boundary.position(&self.structure); + assert!(at <= position, "boundary store answered past the target"); + + // Positioning densifies: every window boundary crossed on the + // way to `position` commits through the store, so the next + // ruler resumes at most one window away. + let stf = stf.with_write_back( + Storage::new(self.store.state_dir())?, + self.epoch, + boundary.0, + ); + let mut ruler = Ruler::new_at(stf, self.structure, self.fed_windows, at); + ruler.advance(position)?; + Ok(ruler) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine::constants; + + /// Drift guard: the engine structure and the machine constants must + /// describe the same ruler. + #[test] + fn production_structure_matches_machine_constants() { + let production = Structure::PRODUCTION; + assert_eq!( + production.log2_uarch_span, + constants::LOG2_UARCH_SPAN_TO_BARCH + ); + assert_eq!( + production.log2_barch_span, + constants::LOG2_BARCH_SPAN_TO_INPUT + ); + assert_eq!( + production.log2_input_span, + constants::LOG2_INPUT_SPAN_TO_EPOCH + ); + } + + /// Drift guard: the coordinate the runner prepays (window-root + /// quartet rows at commit) must be the one the facade's top tier + /// looks up - the source reads rows at (run stride, window + /// height, shift = window) under its production run stride. + #[test] + fn runner_and_facade_agree_on_window_root_coordinates() { + use crate::storage::rollups_machine; + let structure = Structure::PRODUCTION; + let quartet = rollups_machine::window_root_quartet(3, 7); + assert_eq!(quartet.log2_stride, rollups_machine::LOG2_STRIDE); + assert_eq!( + quartet.height, + structure.log2_window_span() - rollups_machine::LOG2_STRIDE + ); + assert_eq!(quartet.shift, U256::from(7)); + assert_eq!(quartet.epoch, 3); + } +} diff --git a/cartesi-rollups/node/src/engine/mod.rs b/cartesi-rollups/node/src/engine/mod.rs new file mode 100644 index 000000000..a108daa16 --- /dev/null +++ b/cartesi-rollups/node/src/engine/mod.rs @@ -0,0 +1,52 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The dispute engine core. As of increment C it is what the Hero +//! runs on: the quartet cache in the node database is the +//! restartable dispute state. +//! +//! An epoch's computation is a ruler of state transitions indexed by +//! meta-cycle. This module addresses merkle nodes over that ruler by +//! quartet (epoch, log2_stride, height, shift), computes them through a +//! geometry engine that is generic over the state-transition function, +//! and caches them in SQLite. The design and its rationale live in +//! docs/plans/sling-design.md; the ruler semantics in +//! docs/computation-hash.md. +//! +//! Layering, innermost first: +//! - [`stf::Stf`]: machine verbs (ustep, ureset, feed, revert). Two +//! implementations: the toy (here, for spec tests) and the Cartesi +//! machine (increment B). +//! - [`ruler::Ruler`]: the geometry engine. Owns every meta-cycle +//! convention (window boundaries, fused feed transition, big-cycle +//! closing ureset, fixed-point padding). Written once, exercised by +//! the toy, reused by the production machine. +//! - [`cache::NodeCache`] and [`cache::get_or_compute`]: the quartet +//! cache with its amortizing fanout. +//! - [`dispute::DisputeSource`]: the hero-facing face. Tournament +//! coordinates map onto quartets ([`dispute::LevelCoords`]), level 0 +//! is served from the persisted regime-1 material (window-root rows +//! plus lazy interior folds), and proofs are sibling descents. +//! +//! The spec tests in `spec.rs` compare all of this against an +//! independent brute-force oracle; they are the executable form of the +//! leaf-convention specification. + +pub mod cache; +pub mod config; +pub mod constants; +pub mod dispute; +pub mod machine_stf; +pub mod ruler; +pub mod stf; +pub mod structure; + +#[cfg(test)] +pub(crate) mod spec; + +pub use config::EngineConfig; +pub use dispute::{DisputeSource, LevelCoords, fold_runs}; +pub use machine_stf::{MachineStf, Positioner}; +pub use ruler::{Ruler, RulerFactory, Run, ToyFactory}; +pub use stf::{ProvingStf, Stf, ToyInput, ToyOutcome, ToyStf}; +pub use structure::{InputBoundary, Position, Quartet, Structure}; diff --git a/cartesi-rollups/node/src/engine/ruler.rs b/cartesi-rollups/node/src/engine/ruler.rs new file mode 100644 index 000000000..70ffea7f9 --- /dev/null +++ b/cartesi-rollups/node/src/engine/ruler.rs @@ -0,0 +1,495 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The geometry engine: drives an [`Stf`] along the ruler, mapping each +//! meta-cycle position to its transition shape and exploiting the +//! periodicity of idle spans so padded regions cost almost nothing. +//! +//! Every meta-cycle convention lives here, once, and nowhere else: +//! +//! - Position p counts applied transitions; the leaf at p is the +//! post-state of transition p. +//! - Window starts (p multiple of the window span): the fused +//! transition, checkpoint write plus input delivery plus the first +//! ustep, when the epoch has an input for that window. Inputs are a +//! contiguous prefix; window w feeds input w. +//! - Big-cycle closing slots (p one short of a big-span multiple): a +//! final (possibly identity) ustep, the ureset, and the revert check. +//! - Everything else: one ustep. +//! - Idle regions (halted forever, or yielded until the next fed +//! window): the machine's big state is a fixed point, but only at +//! big-cycle boundaries. Within each idle big cycle the uarch churns +//! its own bookkeeping (the emulated interpreter checks the flags +//! and declines to execute) until it halts, and the closing ureset +//! restores the base hash exactly. Every idle big cycle therefore +//! repeats one identical leaf pattern, so the engine steps a single +//! idle span and replays it for the whole region. +//! +//! Invariant, with a tripwire: an input's computation never crosses its +//! window boundary. The spans (a, b, c) are deliberate overestimates - +//! far more inputs than a chain can carry (batching makes one input a +//! whole bundle of transactions) and far more big cycles than gas-bounded +//! input processing can consume - so a machine still running at a window +//! start means a broken machine or broken assumptions, and the engine +//! panics rather than inventing a transition shape for it. + +use super::stf::{ProvingStf, Stf, ToyInput, ToyStf}; +use super::structure::Structure; +use crate::merkle::Digest; +use alloy::primitives::U256; +use anyhow::Result; + +/// A run of identical consecutive leaves. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Run { + pub hash: Digest, + pub repetitions: U256, +} + +/// A leaf value the engine reports: the live machine (its hash pulled +/// only when a sample lands) or a known digest from a captured idle +/// pattern. +enum Leaf<'a, S: Stf> { + Live(&'a mut S), + Known(Digest), +} + +impl<'a, S: Stf> Leaf<'a, S> { + fn digest(self) -> Result { + match self { + Leaf::Live(stf) => stf.state_hash(), + Leaf::Known(digest) => Ok(digest), + } + } +} + +pub struct Ruler { + structure: Structure, + stf: S, + /// How many windows feed: inputs are a contiguous prefix, so the + /// geometry needs only the count. Payloads live with the Stf. + fed_windows: u64, + position: U256, +} + +impl Ruler { + /// Takes an stf at the epoch's initial state (position zero). + pub fn new(stf: S, structure: Structure, fed_windows: u64) -> Self { + Self::new_at(stf, structure, fed_windows, U256::ZERO) + } + + /// Takes an stf whose state is the machine at `position` - the + /// caller's provenance contract (a boundary-store answer). The + /// cache's collision tripwire cross-checks computed hashes + /// wherever resumed and replayed runs overlap. + pub fn new_at(stf: S, structure: Structure, fed_windows: u64, position: U256) -> Self { + structure.assert_valid(); + assert!( + fed_windows <= structure.max_inputs(), + "more inputs than the epoch admits" + ); + assert!(position <= structure.ruler_span(), "past the epoch's end"); + Ruler { + structure, + stf, + fed_windows, + position, + } + } + + pub fn position(&self) -> U256 { + self.position + } + + /// Deconstructs into the stf: the forward schedule hands the + /// machine back to its owner between windows. + pub fn into_stf(self) -> S { + self.stf + } + + pub fn state_hash(&mut self) -> Result { + self.stf.state_hash() + } + + /// Advance to `to` without collecting: the big-architecture + /// shortcut for whole big cycles, uarch steps for the remainder. + /// Nothing samples, so nothing is hashed along the way; the coarse + /// leg chunks at window granularity (the only boundaries where the + /// coarse loop must stop anyway). + pub fn advance(&mut self, to: U256) -> Result<()> { + let c = self.structure.log2_uarch_span; + let one = U256::from(1); + let big_ceil = ((self.position + (one << c) - one) >> c) << c; + let big_floor = (to >> c) << c; + if big_ceil < big_floor { + self.step_until(big_ceil, &mut |_, _| Ok(()))?; + let chunk = self.structure.log2_window_span(); + self.coarse_step_until(big_floor, chunk, &mut |_, _| Ok(()))?; + } + self.step_until(to, &mut |_, _| Ok(())) + } + + /// Advance to `to`, sampling the post-state every 2^log2_stride + /// transitions. Position and `to` must be stride-aligned so the + /// samples land on quartet boundaries. Sampling at or above big + /// cycles rides the big architecture; finer sampling steps the + /// uarch. + pub fn collect(&mut self, to: U256, log2_stride: u64) -> Result> { + let stride = U256::from(1) << log2_stride; + assert!((self.position % stride).is_zero(), "unaligned start"); + assert!((to % stride).is_zero(), "unaligned end"); + let mut sampler = StrideSampler::new(self.position, log2_stride); + if log2_stride >= self.structure.log2_uarch_span { + self.coarse_step_until(to, log2_stride, &mut |leaf, count| { + sampler.feed(leaf, count) + })?; + } else { + self.step_until(to, &mut |leaf, count| sampler.feed(leaf, count))?; + } + Ok(sampler.finish()) + } + + /// The core loop. `emit(leaf, n)` reports that the next n + /// transitions all produced `leaf`; consumers pull live hashes only + /// when a sample lands in the run, so unsampled stretches cost no + /// hashing. Whole idle big cycles replay one captured span; every + /// other position, including partial idle spans, executes + /// transition by transition. + fn step_until( + &mut self, + to: U256, + emit: &mut impl FnMut(Leaf<'_, S>, U256) -> Result<()>, + ) -> Result<()> { + assert!(self.position <= to, "ruler cannot move backwards"); + assert!(to <= self.structure.ruler_span(), "past the epoch's end"); + + let big_span = U256::from(self.structure.big_span()); + let one = U256::from(1); + + while self.position < to { + let p = self.structure.decompose(self.position); + let has_input = p.input < self.fed_windows; + let feeds_now = p.is_window_start() && has_input; + + if p.is_big_start() { + let halted = self.stf.halted()?; + if halted || (self.stf.yielded()? && !feeds_now) { + // Idle until the next fed window (never, when + // halted). Whole cycles replay one captured span; a + // trailing partial cycle steps plainly below. + let idle_end = if halted { + to + } else { + let next_window = p.input + 1; + let next_feed = if next_window < self.fed_windows { + self.structure.window_start(next_window) + } else { + to + }; + next_feed.min(to) + }; + let cycles = (idle_end - self.position) / big_span; + if !cycles.is_zero() { + self.replay_idle_cycles(cycles, emit)?; + continue; + } + } else if p.is_window_start() { + if self.stf.yielded()? { + // Fused transition: feed plus the first ustep. + self.stf.feed(p.input)?; + self.stf.ustep()?; + emit(Leaf::Live(&mut self.stf), one)?; + self.position += one; + continue; + } + panic!( + "input overran its window at position {}; \ + transition shape undefined (see module doc)", + self.position + ); + } + } + + if p.is_closing_slot(&self.structure) { + // Closing slot: (identity when uarch already halted) + // ustep, ureset, then the revert check. + self.stf.ustep()?; + self.stf.ureset()?; + if self.stf.yielded()? { + self.stf.revert_if_needed()?; + } + emit(Leaf::Live(&mut self.stf), one)?; + self.position += one; + } else if self.stf.uarch_halted()? { + // Identity usteps until the closing slot. + let skip = + U256::from(self.structure.big_span() - 1 - p.ustep).min(to - self.position); + emit(Leaf::Live(&mut self.stf), skip)?; + self.position += skip; + } else { + self.stf.ustep()?; + emit(Leaf::Live(&mut self.stf), one)?; + self.position += one; + } + } + Ok(()) + } + + /// Emits `cycles` whole idle big cycles from a big-aligned + /// position. Steps one span to capture the churn pattern - the + /// machine ends back at its base state, which is also its exact + /// state at every big boundary of the region - then replays it + /// arithmetically. Output size is proportional to the sampled + /// runs, which is the true leaf structure at sub-big strides. + fn replay_idle_cycles( + &mut self, + cycles: U256, + emit: &mut impl FnMut(Leaf<'_, S>, U256) -> Result<()>, + ) -> Result<()> { + let big_span = U256::from(self.structure.big_span()); + let pattern = self.collect_idle_span()?; + let mut remaining = cycles; + while !remaining.is_zero() { + for run in &pattern { + emit(Leaf::Known(run.hash), run.repetitions)?; + } + self.position += big_span; + remaining -= U256::from(1); + } + Ok(()) + } + + /// One idle uarch span, stepped: churn usteps until the uarch + /// halts, arithmetic padding, and the closing ureset (with its + /// revert check) restoring the base state. + fn collect_idle_span(&mut self) -> Result> { + fn push(runs: &mut Vec, hash: Digest, count: u64) { + match runs.last_mut() { + Some(last) if last.hash == hash => last.repetitions += U256::from(count), + _ => runs.push(Run { + hash, + repetitions: U256::from(count), + }), + } + } + + let big_span = self.structure.big_span(); + let mut runs = vec![]; + let mut slot = 0u64; + while slot < big_span - 1 && !self.stf.uarch_halted()? { + self.stf.ustep()?; + slot += 1; + push(&mut runs, self.stf.state_hash()?, 1); + } + if slot < big_span - 1 { + push(&mut runs, self.stf.state_hash()?, big_span - 1 - slot); + } + // The closing slot. + self.stf.ustep()?; + self.stf.ureset()?; + if self.stf.yielded()? { + self.stf.revert_if_needed()?; + } + push(&mut runs, self.stf.state_hash()?, 1); + Ok(runs) + } +} + +impl Ruler { + /// The coarse loop: valid only when every sampled position is a + /// big-cycle end (log2_stride >= log2_uarch_span). Emits runs at + /// chunk granularity, where a chunk never crosses a sample boundary + /// or a window boundary; intermediate big-cycle hashes inside a + /// chunk are never sampled, so attributing the chunk's end hash to + /// the whole chunk is exact where it matters. An early stop inside + /// a chunk leaves the machine idle, and idle machines carry their + /// base hash at every big boundary, so the end hash is exact there + /// too. + fn coarse_step_until( + &mut self, + to: U256, + log2_stride: u64, + emit: &mut impl FnMut(Leaf<'_, S>, U256) -> Result<()>, + ) -> Result<()> { + let structure = self.structure; + let c = structure.log2_uarch_span; + assert!(log2_stride >= c, "coarse mode needs big-cycle sampling"); + assert!(self.position <= to, "ruler cannot move backwards"); + assert!(to <= structure.ruler_span(), "past the epoch's end"); + assert!( + (self.position % (U256::from(1) << c)).is_zero(), + "coarse mode needs big-cycle alignment" + ); + + let one = U256::from(1); + + while self.position < to { + let p = structure.decompose(self.position); + let has_input = p.input < self.fed_windows; + + if self.stf.halted()? { + let n = to - self.position; + emit(Leaf::Live(&mut self.stf), n)?; + self.position = to; + break; + } + if self.stf.yielded()? { + let feeds_now = p.is_window_start() && has_input; + if !feeds_now { + let next_window = p.input + 1; + let next_feed = if next_window < self.fed_windows { + structure.window_start(next_window) + } else { + to + }; + let stop = next_feed.min(to); + let n = stop - self.position; + assert!(!n.is_zero(), "yielded with nothing to do"); + emit(Leaf::Live(&mut self.stf), n)?; + self.position = stop; + continue; + } + // The feed is part of the window's first transition; its + // fused ustep is subsumed by running big cycle 0 whole. + self.stf.feed(p.input)?; + } else if p.is_window_start() { + panic!( + "input overran its window at position {}; \ + invariant violated (see module doc)", + self.position + ); + } + + let next_sample = ((self.position >> log2_stride) + one) << log2_stride; + let window_end = structure.window_start(p.input + 1); + let chunk_end = next_sample.min(window_end).min(to); + + let mut remaining = (chunk_end - self.position) >> c; + while !remaining.is_zero() { + let batch = if remaining > U256::from(u64::MAX) { + u64::MAX + } else { + u64::try_from(remaining).expect("bounded by u64::MAX") + }; + let ran = self.stf.run_big(batch)?; + remaining -= U256::from(ran); + if ran < batch { + break; + } + } + if self.stf.yielded()? { + self.stf.revert_if_needed()?; + } + emit(Leaf::Live(&mut self.stf), chunk_end - self.position)?; + self.position = chunk_end; + } + Ok(()) + } +} + +impl Ruler { + /// Proves the transition at the current position: the chain + /// witness for exactly one of the three shapes the ruler names, + /// plus the post-transition state hash. The caller positions the + /// ruler (a snapshot resume plus advance) and checks the machine + /// against the on-chain agree hash first; the machine is spent + /// afterwards (the revert check proves without applying). + pub fn prove_transition(&mut self) -> Result<(Vec, Digest)> { + let p = self.structure.decompose(self.position); + + let proof = if p.is_window_start() { + // The window-opening transition: data availability (plus + // checkpoint and delivery when the window feeds) and the + // fused first ustep. + let feed_proof = self.stf.log_feed(p.input)?; + [feed_proof, self.stf.log_ustep()?].concat() + } else if p.is_closing_slot(&self.structure) { + // The closing slot: the (identity) ustep, the ureset, and + // the revert witness. + assert!( + self.stf.uarch_halted()?, + "the uarch must have halted before its closing slot" + ); + [ + self.stf.log_ustep()?, + self.stf.log_ureset()?, + self.stf.log_revert_check()?, + ] + .concat() + } else { + self.stf.log_ustep()? + }; + + Ok((proof, self.stf.state_hash()?)) + } +} + +/// Compresses a stream of per-transition post-state runs into sampled +/// runs at a stride: sample j is the post-state at position +/// (j + 1) * 2^log2_stride - 1. Pulls the state hash only for runs +/// that contain at least one sample. +struct StrideSampler { + log2_stride: u64, + position: U256, + out: Vec, +} + +impl StrideSampler { + fn new(position: U256, log2_stride: u64) -> Self { + StrideSampler { + log2_stride, + position, + out: vec![], + } + } + + fn feed(&mut self, leaf: Leaf<'_, S>, count: U256) -> Result<()> { + let lo = self.position; + let hi = lo + count; + // Samples in [lo, hi) are the positions p with (p + 1) divisible + // by the stride, i.e. the stride multiples in (lo, hi]. + let n = (hi >> self.log2_stride) - (lo >> self.log2_stride); + if !n.is_zero() { + let hash = leaf.digest()?; + match self.out.last_mut() { + Some(last) if last.hash == hash => last.repetitions += n, + _ => self.out.push(Run { + hash, + repetitions: n, + }), + } + } + self.position = hi; + Ok(()) + } + + fn finish(self) -> Vec { + self.out + } +} + +/// Provides rulers positioned anywhere on the epoch. Implementations +/// own the positioning strategy: the toy replays from the start, the +/// machine implementation will resume from the nearest snapshot. +pub trait RulerFactory { + type S: Stf; + fn ruler_at(&mut self, position: U256) -> Result>; +} + +/// Toy factory: each scripted input is one epoch input (payloads are +/// irrelevant to the toy). +pub struct ToyFactory { + pub structure: Structure, + pub script: Vec, +} + +impl RulerFactory for ToyFactory { + type S = ToyStf; + + fn ruler_at(&mut self, position: U256) -> Result> { + let stf = ToyStf::new(self.structure, self.script.clone()); + let mut ruler = Ruler::new(stf, self.structure, self.script.len() as u64); + ruler.advance(position)?; + Ok(ruler) + } +} diff --git a/cartesi-rollups/node/src/engine/spec.rs b/cartesi-rollups/node/src/engine/spec.rs new file mode 100644 index 000000000..7d45f3922 --- /dev/null +++ b/cartesi-rollups/node/src/engine/spec.rs @@ -0,0 +1,907 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The executable leaf-convention specification. +//! +//! `oracle_counters` enumerates the whole ruler by brute force, written +//! directly from the documented conventions with none of the engine's +//! machinery. Every test compares engine and cache outputs against it. +//! If the engine and the oracle ever disagree, the conventions are +//! ambiguous or one of them is wrong; either way the spec is doing its +//! job. + +use super::cache::{PRECOMPUTE_LEVELS, get_or_compute}; +use super::config::EngineConfig; +use super::dispute::{DisputeSource, LevelCoords, fold_runs}; +use super::ruler::{RulerFactory, Run, ToyFactory}; +use super::stf::{IDLE_CHURN_TICKS, ToyInput, ToyOutcome, ToyStf}; +use super::structure::{Quartet, Structure}; +use crate::merkle::{Digest, MerkleBuilder, MerkleTree}; +use crate::storage::Storage; +use alloy::primitives::U256; +use anyhow::Result; +use rusqlite::Connection; +use std::sync::Arc; + +// Tiny structures: (a, b, c) as in docs/computation-hash.md. +const S_DIAGRAM: Structure = Structure { + log2_input_span: 1, + log2_barch_span: 1, + log2_uarch_span: 2, +}; // 16 positions: the toy picture in the docs + +pub(crate) const S_SMALL: Structure = Structure { + log2_input_span: 2, + log2_barch_span: 2, + log2_uarch_span: 3, +}; // 128 positions + +const S_MEDIUM: Structure = Structure { + log2_input_span: 2, + log2_barch_span: 3, + log2_uarch_span: 4, +}; // 512 positions + +fn accept(big_cycles: &[u64]) -> ToyInput { + ToyInput { + big_cycles: big_cycles.to_vec(), + outcome: ToyOutcome::Accept, + } +} + +fn reject(big_cycles: &[u64]) -> ToyInput { + ToyInput { + big_cycles: big_cycles.to_vec(), + outcome: ToyOutcome::Reject, + } +} + +fn halt(big_cycles: &[u64]) -> ToyInput { + ToyInput { + big_cycles: big_cycles.to_vec(), + outcome: ToyOutcome::Halt, + } +} + +/// Scripts covering every geometry case: full activity, early uarch +/// halts, early yields, rejection (revert), machine halt, empty epoch. +fn scripts_for(structure: &Structure) -> Vec<(&'static str, Vec)> { + let max_usteps = structure.big_span() - 1; + let window_bigs = 1u64 << structure.log2_barch_span; + let fully_active = vec![max_usteps; window_bigs as usize]; + + vec![ + ("empty", vec![]), + ("one_full", vec![accept(&fully_active)]), + ("one_short", vec![accept(&[1])]), + ( + "mixed", + vec![ + accept(&[2, max_usteps, 1]), + reject(&[max_usteps, 2]), + accept(&[1]), + ], + ), + ("halting", vec![accept(&[2, 2]), halt(&[1])]), + ("reject_first", vec![reject(&[1]), accept(&[2])]), + ] + .into_iter() + .map(|(name, script)| { + // Clamp scripts that do not fit tiny structures. + let script = script + .into_iter() + .take(structure.max_inputs() as usize) + .map(|mut input| { + input.big_cycles.truncate(window_bigs as usize); + input + }) + .collect(); + (name, script) + }) + .collect() +} + +/// The brute-force spec: the state digest at every ruler position, +/// written as literal nested loops over windows, big cycles, and +/// slots, including the idle churn pattern (see the ruler module doc: +/// idle big cycles repeat churned slots and close back on the base +/// state). +fn oracle_digests(structure: &Structure, script: &[ToyInput]) -> Vec { + let big_span = structure.big_span(); + let window_bigs = 1u64 << structure.log2_barch_span; + let mut leaves = Vec::new(); + let mut state = 0u64; + let mut halted = false; + + // One idle big cycle: the churn ticks color every slot before the + // closing ureset restores the base state. + let idle_cycle = |leaves: &mut Vec, state: u64| { + for _ in 0..big_span - 1 { + leaves.push(ToyStf::churned_hash_of(state, IDLE_CHURN_TICKS)); + } + leaves.push(ToyStf::hash_of(state)); + }; + + for window in 0..structure.max_inputs() { + let scripted = if halted { + None + } else { + script.get(window as usize) + }; + match scripted { + None => { + // No input (or halted): the whole window idles. + for _ in 0..window_bigs { + idle_cycle(&mut leaves, state); + } + } + Some(input) => { + let checkpoint = state; + for (index, &active_usteps) in input.big_cycles.iter().enumerate() { + let last = index + 1 == input.big_cycles.len(); + // Ustep slots: active ones advance, the rest repeat. + for slot in 0..big_span - 1 { + if slot < active_usteps { + state += 1; + } + leaves.push(ToyStf::hash_of(state)); + } + // The ureset slot; the revert lands here when the + // yield rejects. + state += 1; + if last && input.outcome == ToyOutcome::Reject { + state = checkpoint; + } + leaves.push(ToyStf::hash_of(state)); + if last && input.outcome == ToyOutcome::Halt { + halted = true; + } + } + // Window padding after the yield (or halt). + let used = input.big_cycles.len() as u64; + for _ in used..window_bigs { + idle_cycle(&mut leaves, state); + } + } + } + } + leaves +} + +fn expand(runs: &[Run]) -> Vec { + let mut out = vec![]; + for run in runs { + let n = u64::try_from(run.repetitions).expect("test sizes fit u64"); + out.extend(std::iter::repeat_n(run.hash, n as usize)); + } + out +} + +pub(crate) fn toy_storage(structure: Structure) -> Storage { + let config = EngineConfig { + structure, + app: vec![0xda; 20], + template_hash: ToyStf::hash_of(0), + emulator_version: "toy".into(), + }; + let dir = tempfile::tempdir().unwrap().keep(); + let mut connection = Connection::open(dir.join("db.sqlite3")).unwrap(); + crate::storage::sql::migrations::migrate_to_latest(&mut connection).unwrap(); + super::config::pin(&connection, &config).unwrap(); + Storage::new(&dir).unwrap() +} + +#[test] +fn full_ruler_matches_oracle() { + for structure in [S_DIAGRAM, S_SMALL, S_MEDIUM] { + for (name, script) in scripts_for(&structure) { + let expected = oracle_digests(&structure, &script); + let mut factory = ToyFactory { + structure, + script: script.clone(), + }; + let mut ruler = factory.ruler_at(U256::ZERO).unwrap(); + let runs = ruler.collect(structure.ruler_span(), 0).unwrap(); + assert_eq!(expand(&runs), expected, "script {name} on {structure:?}"); + } + } +} + +#[test] +fn stride_sampling_matches_oracle() { + for structure in [S_DIAGRAM, S_SMALL] { + let total = structure.log2_ruler_span(); + for (name, script) in scripts_for(&structure) { + let oracle = oracle_digests(&structure, &script); + for log2_stride in 1..=total { + let stride = 1usize << log2_stride; + let expected: Vec = oracle + .iter() + .skip(stride - 1) + .step_by(stride) + .copied() + .collect(); + let mut factory = ToyFactory { + structure, + script: script.clone(), + }; + let mut ruler = factory.ruler_at(U256::ZERO).unwrap(); + let runs = ruler.collect(structure.ruler_span(), log2_stride).unwrap(); + assert_eq!( + expand(&runs), + expected, + "script {name}, stride 2^{log2_stride}" + ); + } + } + } +} + +#[test] +fn fully_active_state_is_position_plus_one() { + // The property the toy is named for: with no padding, the state + // after transition N is N + 1. + let structure = S_SMALL; + let window_bigs = 1usize << structure.log2_barch_span; + let script = vec![accept(&vec![structure.big_span() - 1; window_bigs])]; + let oracle = oracle_digests(&structure, &script); + let window_span = u64::try_from(structure.window_span()).unwrap(); + for (position, digest) in oracle.iter().enumerate().take(window_span as usize) { + assert_eq!(*digest, ToyStf::hash_of(position as u64 + 1)); + } +} + +#[test] +fn mid_span_positioning_matches_oracle() { + // A ruler positioned mid-epoch by replay must continue exactly + // where the oracle says it should. + let structure = S_SMALL; + for (name, script) in scripts_for(&structure) { + let oracle = oracle_digests(&structure, &script); + let quarter = structure.ruler_span() >> 2; + let mut factory = ToyFactory { + structure, + script: script.clone(), + }; + let mut ruler = factory.ruler_at(quarter).unwrap(); + let runs = ruler.collect(quarter * U256::from(3), 0).unwrap(); + let lo = u64::try_from(quarter).unwrap() as usize; + let hi = lo * 3; + assert_eq!(expand(&runs), oracle[lo..hi], "script {name}"); + } +} + +#[test] +fn cache_root_matches_oracle_tree() -> Result<()> { + for structure in [S_DIAGRAM, S_SMALL] { + for (name, script) in scripts_for(&structure) { + let mut cache = toy_storage(structure); + let mut factory = ToyFactory { + structure, + script: script.clone(), + }; + + let root = Quartet::level_root(0, 0, structure.log2_ruler_span()); + let computed = get_or_compute(&mut cache, &structure, &mut factory, &root)?; + + let mut builder = MerkleBuilder::default(); + for digest in oracle_digests(&structure, &script) { + builder.append(digest); + } + assert_eq!( + computed, + builder.build().root_hash(), + "script {name} on {structure:?}" + ); + } + } + Ok(()) +} + +#[test] +fn coarse_root_equals_sampled_oracle_tree() -> Result<()> { + // A commitment at a coarse stride is the tree over the sampled + // oracle leaves, matching how tournament levels see the epoch. + let structure = S_MEDIUM; + let (_, script) = scripts_for(&structure).remove(3); // mixed + let log2_stride = structure.log2_uarch_span; // big-cycle stride + let height = structure.log2_ruler_span() - log2_stride; + + let mut cache = toy_storage(structure); + let mut factory = ToyFactory { + structure, + script: script.clone(), + }; + let root = Quartet::level_root(0, log2_stride, height); + let computed = get_or_compute(&mut cache, &structure, &mut factory, &root)?; + + let stride = 1usize << log2_stride; + let mut builder = MerkleBuilder::default(); + for digest in oracle_digests(&structure, &script) + .into_iter() + .skip(stride - 1) + .step_by(stride) + { + builder.append(digest); + } + assert_eq!(computed, builder.build().root_hash()); + Ok(()) +} + +#[test] +fn children_join_to_parent() -> Result<()> { + let structure = S_SMALL; + let (_, script) = scripts_for(&structure).remove(3); // mixed + let mut cache = toy_storage(structure); + let mut factory = ToyFactory { structure, script }; + + let mut quartet = Quartet::level_root(0, 0, structure.log2_ruler_span()); + while let Some((left, right)) = quartet.children() { + let parent = get_or_compute(&mut cache, &structure, &mut factory, &quartet)?; + let l = get_or_compute(&mut cache, &structure, &mut factory, &left)?; + let r = get_or_compute(&mut cache, &structure, &mut factory, &right)?; + assert_eq!(parent, l.join(&r), "at {quartet:?}"); + // Descend along the right edge, crossing fanout strata. + quartet = right; + } + Ok(()) +} + +/// Counts how often the cache had to touch the (toy) machine. +struct Counting { + inner: ToyFactory, + calls: usize, +} + +impl RulerFactory for Counting { + type S = ToyStf; + fn ruler_at(&mut self, position: U256) -> Result> { + self.calls += 1; + self.inner.ruler_at(position) + } +} + +#[test] +fn fanout_amortizes_descent() -> Result<()> { + let structure = S_MEDIUM; // ruler height 9 crosses one fanout stratum + let (_, script) = scripts_for(&structure).remove(3); + let mut cache = toy_storage(structure); + let mut factory = Counting { + inner: ToyFactory { structure, script }, + calls: 0, + }; + + let root = Quartet::level_root(0, 0, structure.log2_ruler_span()); + get_or_compute(&mut cache, &structure, &mut factory, &root)?; + assert_eq!(factory.calls, 1); + + // Everything within PRECOMPUTE_LEVELS of the root is already there. + let mut quartet = root.clone(); + for _ in 0..PRECOMPUTE_LEVELS { + let (left, _) = quartet.children().unwrap(); + get_or_compute(&mut cache, &structure, &mut factory, &left)?; + quartet = left; + } + assert_eq!(factory.calls, 1, "descent within the fanout hit the cache"); + + // One level further misses and costs exactly one more machine trip. + let (left, _) = quartet.children().unwrap(); + get_or_compute(&mut cache, &structure, &mut factory, &left)?; + assert_eq!(factory.calls, 2); + + // Repeating any of it stays cached. + get_or_compute(&mut cache, &structure, &mut factory, &root)?; + get_or_compute(&mut cache, &structure, &mut factory, &left)?; + assert_eq!(factory.calls, 2); + Ok(()) +} + +#[test] +fn empty_epoch_is_iterated_initial_state_at_big_stride() -> Result<()> { + // At big-cycle strides an empty epoch samples only big boundaries, + // which all carry the initial state - the iterated tree the + // settlement layer builds. At uarch stride the same epoch carries + // the idle churn pattern, covered by the oracle tests. + let structure = S_SMALL; + let mut cache = toy_storage(structure); + let mut factory = ToyFactory { + structure, + script: vec![], + }; + let log2_stride = structure.log2_uarch_span; + let height = structure.log2_ruler_span() - log2_stride; + let root = Quartet::level_root(0, log2_stride, height); + let computed = get_or_compute(&mut cache, &structure, &mut factory, &root)?; + + let expected = MerkleTree::leaf(ToyStf::hash_of(0)) + .iterated(height as usize) + .root_hash(); + assert_eq!(computed, expected); + Ok(()) +} + +#[test] +fn reject_restores_the_checkpoint() { + // After a rejected input, the window tail idles over the pre-window + // state, and the next window builds on it. + let structure = S_SMALL; + let script = vec![reject(&[3]), accept(&[2])]; + let oracle = oracle_digests(&structure, &script); + let window = u64::try_from(structure.window_span()).unwrap() as usize; + let big = structure.big_span() as usize; + + // Window 0 processes and reverts: its last leaf is the checkpoint. + assert_eq!( + oracle[big - 1], + ToyStf::hash_of(0), + "revert lands on the closing slot" + ); + // The tail idles over the checkpoint: churn inside each big cycle, + // the checkpoint itself at each big boundary. + assert_eq!( + oracle[window - 2], + ToyStf::churned_hash_of(0, IDLE_CHURN_TICKS), + "tail slots churn over the checkpoint" + ); + assert_eq!( + oracle[window - 1], + ToyStf::hash_of(0), + "tail boundaries repeat the checkpoint" + ); + // Window 1 resumes counting from the restored state. + assert_eq!( + oracle[window], + ToyStf::hash_of(1), + "next input builds on restored state" + ); +} + +// +// Dispute-source spec: the hero-facing queries must agree with an +// in-memory reference tree built from the (oracle-checked) ruler runs. +// + +/// The reference: a whole level materialized as one in-memory tree. +fn reference_tree( + structure: Structure, + script: &[ToyInput], + level: &LevelCoords, +) -> Arc { + let mut factory = ToyFactory { + structure, + script: script.to_vec(), + }; + let mut ruler = factory.ruler_at(level.base_cycle).unwrap(); + let span = U256::from(1) << (level.log2_stride + level.height); + let runs = ruler + .collect(level.base_cycle + span, level.log2_stride) + .unwrap(); + let mut builder = MerkleBuilder::default(); + for run in &runs { + builder.append_repeated(run.hash, run.repetitions); + } + builder.build() +} + +fn reference_node(tree: &Arc, depth: u64, index: U256) -> Arc { + let mut node = Arc::clone(tree); + for i in (0..depth).rev() { + let (left, right) = node.subtrees().expect("depth bounded by height"); + node = if ((index >> i) & U256::from(1)).is_zero() { + left + } else { + right + }; + } + node +} + +pub(crate) fn toy_source(structure: Structure, script: &[ToyInput]) -> DisputeSource { + toy_source_over( + toy_storage(structure), + structure, + script, + structure.log2_uarch_span, + ) +} + +pub(crate) fn toy_source_over( + storage: Storage, + structure: Structure, + script: &[ToyInput], + log2_run_stride: u64, +) -> DisputeSource { + let factory = ToyFactory { + structure, + script: script.to_vec(), + }; + DisputeSource::new(storage, factory, 0, log2_run_stride).unwrap() +} + +/// Records what the open regime leaves behind for a closed toy +/// epoch, through the production shapes: the input rows (the +/// frontier count), one window-root quartet row per input (folded +/// from a window-sized collect, exactly as the advance commit does), +/// and the final boundary row (the padding value). +fn record_toy_material( + storage: &mut Storage, + structure: &Structure, + script: &[ToyInput], + log2_stride: u64, +) -> Result<()> { + use crate::storage::{Epoch, Input, InputId}; + use alloy::primitives::Address; + + let interior_height = structure.log2_window_span() - log2_stride; + let count = script.len() as u64; + + let inputs: Vec = (0..count) + .map(|i| Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: i, + }, + data: vec![], + }) + .collect(); + storage.insert_consensus_data( + 0, + inputs.iter(), + [&Epoch { + epoch_number: 0, + input_index_boundary: count, + root_tournament: Address::ZERO, + block_created_number: 0, + }] + .into_iter(), + )?; + + let mut factory = ToyFactory { + structure: *structure, + script: script.to_vec(), + }; + let mut ruler = factory.ruler_at(U256::ZERO)?; + for window in 0..count { + let runs = ruler.collect(structure.window_start(window + 1), log2_stride)?; + let root = fold_runs( + runs.iter().map(|run| { + ( + run.hash, + u64::try_from(run.repetitions).expect("window-sized"), + ) + }), + interior_height, + )? + .root_hash(); + storage.insert_quartet_nodes(&[( + Quartet { + epoch: 0, + log2_stride, + height: interior_height, + shift: U256::from(window), + }, + root, + )])?; + } + + // The final boundary row: the toy's state at the frontier (the + // path is never loaded by these tests). + let final_hash = ruler.state_hash()?; + storage.insert_boundary(0, count, &final_hash.data(), std::path::Path::new("/toy"))?; + Ok(()) +} + +#[test] +fn dispute_nodes_match_reference_everywhere() -> Result<()> { + // Every positional node of a level, at every height, against the + // reference subtree; exercises cache hits, misses, and fanout + // stratum crossings alike. + let structure = S_MEDIUM; + for (name, script) in scripts_for(&structure) { + let level = LevelCoords::new(0, U256::ZERO, 0, structure.log2_ruler_span()); + let reference = reference_tree(structure, &script, &level); + let mut source = toy_source(structure, &script); + + for height in (0..=level.height).rev() { + let count = 1u64 << (level.height - height); + for i in 0..count { + let offset = U256::from(i) << height; + let quartet = level.node(height, offset); + let expected = reference_node(&reference, level.height - height, U256::from(i)); + assert_eq!( + source.node(&quartet)?, + expected.root_hash(), + "script {name}, height {height}, offset {offset}" + ); + if height > 0 { + let (l, r) = source.children(&quartet)?; + let (el, er) = expected.subtrees().unwrap(); + assert_eq!((l, r), (el.root_hash(), er.root_hash())); + } + } + } + } + Ok(()) +} + +#[test] +fn dispute_proofs_match_reference_at_every_index() -> Result<()> { + let structure = S_SMALL; + for (name, script) in scripts_for(&structure) { + let level = LevelCoords::new(0, U256::ZERO, 0, structure.log2_ruler_span()); + let reference = reference_tree(structure, &script, &level); + let mut source = toy_source(structure, &script); + + let leaves = 1u64 << level.height; + for i in 0..leaves { + let proof = source.prove_leaf(&level, U256::from(i))?; + let expected = reference.prove_leaf(U256::from(i)); + assert_eq!(proof.position, expected.position, "script {name}, leaf {i}"); + assert_eq!(proof.node, expected.node, "script {name}, leaf {i}"); + assert_eq!(proof.siblings, expected.siblings, "script {name}, leaf {i}"); + assert!(proof.verify_root(reference.root_hash())); + } + let last = source.prove_last(&level)?; + assert_eq!(last.position, U256::from(leaves - 1)); + assert!(last.verify_root(reference.root_hash())); + } + Ok(()) +} + +#[test] +fn sub_level_at_nonzero_base_matches_reference() -> Result<()> { + // An inner tournament's level: window 1 of the epoch at uarch + // stride, pinning the base-cycle shift arithmetic on quartets. + let structure = S_MEDIUM; + let (_, script) = scripts_for(&structure).remove(3); // mixed + let base = structure.window_span(); + let level = LevelCoords::new(0, base, 0, structure.log2_window_span()); + let reference = reference_tree(structure, &script, &level); + let mut source = toy_source(structure, &script); + + assert_eq!(source.node(&level.root())?, reference.root_hash()); + let leaves = 1u64 << level.height; + for i in 0..leaves { + let proof = source.prove_leaf(&level, U256::from(i))?; + let expected = reference.prove_leaf(U256::from(i)); + assert_eq!(proof.node, expected.node, "leaf {i}"); + assert_eq!(proof.siblings, expected.siblings, "leaf {i}"); + } + Ok(()) +} + +#[test] +fn frontier_fold_serves_window_granularity_without_the_machine() -> Result<()> { + // Everything at or above window granularity - the recorded + // prefix, the padding suffix (mixed records 3 of S_MEDIUM's 4), + // and every node whose span crosses the frontier - comes from the + // prepaid window-root rows plus fixed-point arithmetic: same + // answers as the reference, zero machine trips. Below window + // granularity the machine regime takes over (like any nested + // level), so full proof descents stay correct but are allowed to + // replay. + let structure = S_MEDIUM; + let (_, script) = scripts_for(&structure).remove(3); // mixed + let log2_stride = structure.log2_uarch_span; + let interior_height = structure.log2_window_span() - log2_stride; + let level = LevelCoords::new( + 0, + U256::ZERO, + log2_stride, + structure.log2_ruler_span() - log2_stride, + ); + + let mut storage = toy_storage(structure); + record_toy_material(&mut storage, &structure, &script, log2_stride)?; + let state_dir = storage.state_dir().to_path_buf(); + + let reference = reference_tree(structure, &script, &level); + let mut counting = DisputeSource::new( + storage, + Counting { + inner: ToyFactory { + structure, + script: script.clone(), + }, + calls: 0, + }, + 0, + log2_stride, + )?; + + // The frontier fold's whole domain: every node at or above window + // granularity, checked against the reference with the machine + // forbidden. + for height in (interior_height..=level.height).rev() { + let count = 1u64 << (level.height - height); + for i in 0..count { + let quartet = level.node(height, U256::from(i) << height); + let expected = reference_node(&reference, level.height - height, U256::from(i)); + assert_eq!( + counting.node(&quartet)?, + expected.root_hash(), + "height {height}, index {i}" + ); + } + } + assert_eq!( + counting.factory().calls, + 0, + "window granularity and above must not touch the machine" + ); + + // Below the window roots the machine regime serves; proofs cross + // both domains and must still match the reference exactly. + let leaves = 1u64 << level.height; + for i in 0..leaves { + let proof = counting.prove_leaf(&level, U256::from(i))?; + let expected = reference.prove_leaf(U256::from(i)); + assert_eq!(proof.node, expected.node, "leaf {i}"); + assert_eq!(proof.siblings, expected.siblings, "leaf {i}"); + } + + // Those descents bought padding-window roots from the machine and + // stored them AT the window-root coordinate - legitimate final + // rows beyond the recorded prefix. A fresh source over the same + // store must still construct and agree: counting them once + // bricked every reconstruction after the hero's own join (the + // prove_last descent crosses the last padding window). + let mut rebuilt = toy_source_over( + Storage::new(&state_dir).unwrap(), + structure, + &script, + log2_stride, + ); + assert_eq!(rebuilt.node(&level.root())?, reference.root_hash()); + Ok(()) +} + +#[test] +fn full_capacity_frontier_serves_without_padding() -> Result<()> { + // Every window recorded (4 of S_MEDIUM's 4): the frontier fold's + // no-padding branch, against the reference, machine forbidden at + // window granularity and above. + let structure = S_MEDIUM; + let script = vec![accept(&[2, 1]), reject(&[1]), accept(&[3]), accept(&[1, 1])]; + let log2_stride = structure.log2_uarch_span; + let interior_height = structure.log2_window_span() - log2_stride; + let level = LevelCoords::new( + 0, + U256::ZERO, + log2_stride, + structure.log2_ruler_span() - log2_stride, + ); + + let mut storage = toy_storage(structure); + record_toy_material(&mut storage, &structure, &script, log2_stride)?; + + let reference = reference_tree(structure, &script, &level); + let mut counting = DisputeSource::new( + storage, + Counting { + inner: ToyFactory { + structure, + script: script.clone(), + }, + calls: 0, + }, + 0, + log2_stride, + )?; + + assert_eq!(counting.node(&level.root())?, reference.root_hash()); + for window in 0..script.len() as u64 { + let quartet = level.node(interior_height, U256::from(window) << interior_height); + let expected = reference_node( + &reference, + level.height - interior_height, + U256::from(window), + ); + assert_eq!( + counting.node(&quartet)?, + expected.root_hash(), + "window {window}" + ); + } + assert_eq!( + counting.factory().calls, + 0, + "no machine at window granularity" + ); + + let last = counting.prove_last(&level)?; + assert!(last.verify_root(reference.root_hash())); + Ok(()) +} + +#[test] +#[should_panic(expected = "corruption or version drift")] +fn missing_window_root_fails_loudly() { + // Strict rows: a recorded epoch whose window-root row is absent + // is corruption or version drift, and serving must PANIC - the + // tick loops retry errors forever, so only a panic reaches the + // node's loud exit path (lib.rs worker_failure). + let structure = S_MEDIUM; + let (_, script) = scripts_for(&structure).remove(3); // mixed + let log2_stride = structure.log2_uarch_span; + + let mut storage = toy_storage(structure); + record_toy_material(&mut storage, &structure, &script, log2_stride).unwrap(); + + // A hole: delete one prepaid row through a raw connection (the + // settled-epoch prune is the only blessed delete, so borrow its + // shape). + let raw = + rusqlite::Connection::open(crate::storage::open::db_path(storage.state_dir())).unwrap(); + raw.execute( + "DELETE FROM sling_nodes WHERE epoch <= 0 AND shift = ?1", + [U256::from(1).to_be_bytes::<32>().to_vec()], + ) + .unwrap(); + + let factory = ToyFactory { + structure, + script: script.to_vec(), + }; + let _ = DisputeSource::new(storage, factory, 0, log2_stride); +} + +#[test] +fn no_material_serves_through_the_machine() -> Result<()> { + // An epoch that recorded nothing (the empty epoch) has no level-0 + // material: the tiers stand down and the machine serves every + // span as a fixed point of the initial state. + let structure = S_SMALL; + let log2_stride = structure.log2_uarch_span; + let level = LevelCoords::new( + 0, + U256::ZERO, + log2_stride, + structure.log2_ruler_span() - log2_stride, + ); + let reference = reference_tree(structure, &[], &level); + let mut counting = DisputeSource::new( + toy_storage(structure), + Counting { + inner: ToyFactory { + structure, + script: vec![], + }, + calls: 0, + }, + 0, + log2_stride, + )?; + + assert_eq!(counting.node(&level.root())?, reference.root_hash()); + assert!( + counting.factory().calls > 0, + "no material means machine trips" + ); + Ok(()) +} + +#[test] +#[should_panic(expected = "node cache collision")] +fn collision_fails_loudly() { + // Two different computations (scripts) sharing one cache model + // nondeterminism: the second must not overwrite the first, and + // the disagreement must PANIC - the tick loops retry errors + // forever, so only a panic reaches the node's loud exit path. + let structure = S_DIAGRAM; + let mut cache = toy_storage(structure); + let root = Quartet::level_root(0, 0, structure.log2_ruler_span()); + let (left, _) = root.children().unwrap(); + + let mut factory_a = ToyFactory { + structure, + script: vec![accept(&[1])], + }; + get_or_compute(&mut cache, &structure, &mut factory_a, &left).unwrap(); + + let mut factory_b = ToyFactory { + structure, + script: vec![accept(&[2, 2])], + }; + let _ = get_or_compute(&mut cache, &structure, &mut factory_b, &root); +} diff --git a/cartesi-rollups/node/src/engine/stf.rs b/cartesi-rollups/node/src/engine/stf.rs new file mode 100644 index 000000000..12b284e8e --- /dev/null +++ b/cartesi-rollups/node/src/engine/stf.rs @@ -0,0 +1,360 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The machine-verbs boundary between the geometry engine and a concrete +//! state-transition function. +//! +//! The Ruler orchestrates these verbs by meta-cycle position; the +//! implementation supplies the machine mechanics. Production wraps the +//! Cartesi machine; the toy makes the geometry hand-checkable. Verbs are +//! fallible: machine errors propagate as errors, while geometry +//! violations (a feed on a running machine, a ureset off-boundary) +//! remain panics - those are engine bugs, not machine conditions. + +use super::structure::Structure; +use crate::merkle::Digest; +use anyhow::Result; + +pub trait Stf { + /// Hash of the current state. This is what commitment leaves are + /// made of. + fn state_hash(&mut self) -> Result; + + /// Machine halted: a fixed point forever. + fn halted(&mut self) -> Result; + + /// Machine yielded awaiting input: a fixed point until the next fed + /// input. + fn yielded(&mut self) -> Result; + + /// The uarch finished emulating the current big instruction; usteps + /// are identity until the closing ureset. + fn uarch_halted(&mut self) -> Result; + + /// Feed window `window`'s input: the checkpoint write plus the + /// input delivery. Valid only when yielded and not halted, and + /// only for windows that feed (the geometry passes the window + /// index; the implementation owns the payloads - the machine + /// fetches from the input store, the toy consults its script - + /// and cross-checks the index against its own cursor). Its state + /// change surfaces through the post-state of the fused first + /// ustep. + fn feed(&mut self, window: u64) -> Result<()>; + + /// One uarch cycle. Identity only when the uarch is halted: the + /// big machine's yield and halt flags do not gate the uarch, so + /// stepping an idle machine churns the uarch's own bookkeeping (the + /// emulated interpreter checks the flags and declines to execute) + /// until the uarch halts, without touching the big state. + fn ustep(&mut self) -> Result<()>; + + /// Reset the uarch, completing a big cycle. On an idle machine the + /// post-reset state equals the state before the span: idle churn is + /// uarch-local, which is what makes idle spans periodic. + fn ureset(&mut self) -> Result<()>; + + /// Restore the checkpoint if the machine yielded rejecting the last + /// fed input. Returns whether a revert happened. + fn revert_if_needed(&mut self) -> Result; + + /// The big-architecture shortcut: run up to `big_cycles` whole big + /// cycles, stopping early at yield or halt, returning how many ran. + /// The machine-swapping equivalence makes one big step identical to + /// a full uarch span plus its reset, so implementations may run the + /// big machine directly. Idle cycles do not count: the big machine + /// does not advance while yielded or halted (idle uarch spans are + /// state-preserving, so skipping them is exact at big boundaries). + /// The default composes the uarch verbs. + fn run_big(&mut self, big_cycles: u64) -> Result { + let mut executed = 0; + while executed < big_cycles && !self.halted()? && !self.yielded()? { + while !self.uarch_halted()? { + self.ustep()?; + } + self.ureset()?; + executed += 1; + } + Ok(executed) + } +} + +/// The proving verbs: each mirrors a plain verb, performing the same +/// state change while emitting the chain-encoded witness the on-chain +/// state transition consumes. The byte layout is consensus-critical - +/// it must match what prt/contracts' state-transition decodes - and is +/// pinned by a differential test against the prototype proof path plus +/// the stf e2e scenarios, which drive every shape through the chain. +/// +/// Only the real machine's witnesses mean anything to the chain. The +/// toy implements these verbs with inert marker bytes so the proof +/// PATH (positioning, the agree-state check, shape selection) can run +/// under the toy in unit tests; nothing consumes toy bytes. +pub trait ProvingStf: Stf { + /// The window-opening witness: the data-availability encoding of + /// window `window`'s input (empty when the window has none) and, + /// when it does, the checkpoint write proof and the input + /// delivery log, performing both. The implementation resolves + /// the window to its payload, as with [`Stf::feed`]. The fused + /// first ustep is logged separately by [`ProvingStf::log_ustep`]. + fn log_feed(&mut self, window: u64) -> Result>; + + /// One uarch cycle, with its access log. + fn log_ustep(&mut self) -> Result>; + + /// The uarch reset, with its access log. + fn log_ureset(&mut self) -> Result>; + + /// The closing slot's revert witness: proves the yield flag, and + /// on a yielded machine the outcome word plus - if the input was + /// rejected - the checkpoint the chain restores from. Pure reads: + /// the proving path never applies the revert, matching the + /// prototype (the machine is discarded after the proof). + fn log_revert_check(&mut self) -> Result>; +} + +/// How a toy input's processing ends. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ToyOutcome { + Accept, + Reject, + Halt, +} + +/// Per-input script: how many active usteps each big cycle runs before +/// its uarch halts (the remaining slots repeat, the last slot is the +/// ureset), and how processing ends after the final big cycle. The +/// final big cycle models the yield (or halt) instruction itself, so +/// the machine is yielded (halted) as of that cycle's ureset. +#[derive(Debug, Clone)] +pub struct ToyInput { + pub big_cycles: Vec, + pub outcome: ToyOutcome, +} + +/// Idle churn ticks per idle uarch span: how many usteps the toy's +/// "interpreter" spends noticing the machine is yielded or halted +/// before its uarch halts. The real machine spends a few dozen; one +/// tick keeps toy trees hand-computable while modeling the shape. +pub const IDLE_CHURN_TICKS: u64 = 1; + +/// The toy state-transition function. Its state hash is a counter +/// encoded as bytes32, incremented on every state-changing transition, +/// so on a fully active script the state after transition N is N + 1 +/// (with initial state 0). A revert restores the counter to the +/// window's checkpoint. Idle spans (machine yielded or halted) churn a +/// uarch-local tick that colors the hash without touching the counter; +/// the ureset clears it, restoring the base hash, mirroring the real +/// machine's idle periodicity. This keeps expected trees computable by +/// hand in the spec tests. +#[derive(Debug, Clone)] +pub struct ToyStf { + script: Vec, + + counter: u64, + halted: bool, + yielded: bool, + uarch_halted: bool, + /// Idle churn ticks since the last ureset; nonzero only inside an + /// idle uarch span. + uticks: u64, + + // Current input bookkeeping, valid between feed and yield/halt. + fed: usize, + checkpoint: u64, + outcome: ToyOutcome, + big_cycles: Vec, + current_big_cycle: usize, + usteps_in_big_cycle: u64, + reverted: bool, +} + +impl ToyStf { + /// A pristine toy at the epoch's start: yielded, awaiting input 0, + /// counter (and thus implicit hash) zero. + pub fn new(structure: Structure, script: Vec) -> Self { + structure.assert_valid(); + for input in &script { + assert!(!input.big_cycles.is_empty(), "input needs a big cycle"); + assert!( + input.big_cycles[0] >= 1, + "big cycle 0 needs an active ustep (the fused feed)" + ); + for &k in &input.big_cycles { + assert!( + k < structure.big_span(), + "usteps must fit before the ureset slot" + ); + } + } + assert!( + IDLE_CHURN_TICKS < structure.big_span() - 1, + "idle churn must fit before the closing slot" + ); + ToyStf { + script, + counter: 0, + halted: false, + yielded: true, + uarch_halted: false, + uticks: 0, + fed: 0, + checkpoint: 0, + outcome: ToyOutcome::Accept, + big_cycles: vec![], + current_big_cycle: 0, + usteps_in_big_cycle: 0, + reverted: false, + } + } + + pub fn counter(&self) -> u64 { + self.counter + } + + /// The hash of a base state (no idle churn in flight). + pub fn hash_of(counter: u64) -> Digest { + Self::churned_hash_of(counter, 0) + } + + /// The hash of a state mid-idle-span: the counter colored by the + /// uarch-local churn ticks. + pub fn churned_hash_of(counter: u64, uticks: u64) -> Digest { + let mut data = [0u8; 32]; + data[16..24].copy_from_slice(&uticks.to_be_bytes()); + data[24..].copy_from_slice(&counter.to_be_bytes()); + Digest::from_digest(&data).expect("32 bytes") + } + + fn fixed(&self) -> bool { + self.halted || self.yielded + } +} + +impl Stf for ToyStf { + fn state_hash(&mut self) -> Result { + Ok(Self::churned_hash_of(self.counter, self.uticks)) + } + + fn halted(&mut self) -> Result { + Ok(self.halted) + } + + fn yielded(&mut self) -> Result { + Ok(self.yielded) + } + + fn uarch_halted(&mut self) -> Result { + Ok(self.uarch_halted) + } + + fn feed(&mut self, window: u64) -> Result<()> { + assert!( + self.yielded && !self.halted, + "feed requires a yielded machine" + ); + assert_eq!( + window as usize, self.fed, + "windows feed sequentially from the resume point" + ); + let scripted = self + .script + .get(self.fed) + .expect("toy script must cover every fed input") + .clone(); + self.fed += 1; + self.checkpoint = self.counter; + self.outcome = scripted.outcome; + self.big_cycles = scripted.big_cycles; + self.current_big_cycle = 0; + self.usteps_in_big_cycle = 0; + self.yielded = false; + self.uarch_halted = false; + self.reverted = false; + Ok(()) + } + + fn ustep(&mut self) -> Result<()> { + if self.uarch_halted { + return Ok(()); + } + if self.fixed() { + // Idle churn: uarch-local only. + self.uticks += 1; + if self.uticks == IDLE_CHURN_TICKS { + self.uarch_halted = true; + } + return Ok(()); + } + self.counter += 1; + self.usteps_in_big_cycle += 1; + if self.usteps_in_big_cycle == self.big_cycles[self.current_big_cycle] { + self.uarch_halted = true; + } + Ok(()) + } + + fn ureset(&mut self) -> Result<()> { + if self.fixed() { + // An idle span closes: the churn unwinds, the base state + // returns, and the script does not progress. + assert!(self.uarch_halted, "idle churn must halt the uarch"); + self.uticks = 0; + self.uarch_halted = false; + return Ok(()); + } + assert!( + self.uarch_halted, + "toy script must halt the uarch before the ureset slot" + ); + self.counter += 1; + self.uarch_halted = false; + self.usteps_in_big_cycle = 0; + self.current_big_cycle += 1; + if self.current_big_cycle == self.big_cycles.len() { + // This big cycle was the yield (or halt) instruction. + match self.outcome { + ToyOutcome::Accept | ToyOutcome::Reject => self.yielded = true, + ToyOutcome::Halt => self.halted = true, + } + } + Ok(()) + } + + fn revert_if_needed(&mut self) -> Result { + if self.yielded && self.outcome == ToyOutcome::Reject && !self.reverted { + self.counter = self.checkpoint; + self.reverted = true; + Ok(true) + } else { + Ok(false) + } + } +} + +/// Toy witnesses: inert marker bytes over the exact plain-verb state +/// changes, so the Hero's proof path (positioning, agree-state check, +/// shape selection) runs under the toy. Tests may assert which shape +/// was proved from the markers alone. +impl ProvingStf for ToyStf { + fn log_feed(&mut self, window: u64) -> Result> { + if (window as usize) < self.script.len() { + self.feed(window)?; + } + Ok(b"toy-feed;".to_vec()) + } + + fn log_ustep(&mut self) -> Result> { + self.ustep()?; + Ok(b"toy-ustep;".to_vec()) + } + + fn log_ureset(&mut self) -> Result> { + self.ureset()?; + Ok(b"toy-ureset;".to_vec()) + } + + fn log_revert_check(&mut self) -> Result> { + // Pure reads, like the real verb: prove without applying. + Ok(b"toy-revert-check;".to_vec()) + } +} diff --git a/cartesi-rollups/node/src/engine/structure.rs b/cartesi-rollups/node/src/engine/structure.rs new file mode 100644 index 000000000..06dc1a64b --- /dev/null +++ b/cartesi-rollups/node/src/engine/structure.rs @@ -0,0 +1,353 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The ruler's coordinate system. +//! +//! A position on the ruler is the number of transitions applied since +//! the epoch's initial state. The leaf at position m is the state hash +//! after transition m; the state before leaf 0 rides outside the tree +//! (the commitment's implicit hash). + +use crate::engine::constants; +use alloy::primitives::U256; + +/// The structural shape of the state-transition function: the log2 +/// spans of the ruler's three fields. Fixed by the machine at +/// deployment, never by the node; tournament level parameters and cache +/// strides are choices layered on top of it. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Structure { + /// Maximum inputs in an epoch (a): log2. + pub log2_input_span: u64, + /// Maximum big cycles an input may take (b): log2. + pub log2_barch_span: u64, + /// Uarch slots in a big cycle, including the closing ureset (c): log2. + pub log2_uarch_span: u64, +} + +impl Structure { + /// The production shape, derived from the one span authority + /// (engine/constants.rs). + pub const PRODUCTION: Structure = Structure { + log2_input_span: constants::LOG2_INPUT_SPAN_TO_EPOCH, + log2_barch_span: constants::LOG2_BARCH_SPAN_TO_INPUT, + log2_uarch_span: constants::LOG2_UARCH_SPAN_TO_BARCH, + }; + + pub fn log2_ruler_span(&self) -> u64 { + self.log2_input_span + self.log2_barch_span + self.log2_uarch_span + } + + /// Meta-cycles in one input window. + pub fn log2_window_span(&self) -> u64 { + self.log2_barch_span + self.log2_uarch_span + } + + pub fn window_span(&self) -> U256 { + U256::from(1) << self.log2_window_span() + } + + /// Uarch slots in one big cycle. Bounded by u64 (c < 64 always). + pub fn big_span(&self) -> u64 { + 1u64 << self.log2_uarch_span + } + + pub fn ruler_span(&self) -> U256 { + U256::from(1) << self.log2_ruler_span() + } + + pub fn max_inputs(&self) -> u64 { + 1u64 << self.log2_input_span + } + + pub fn assert_valid(&self) { + assert!(self.log2_uarch_span >= 1, "big cycle needs a ureset slot"); + assert!(self.log2_ruler_span() < 256, "ruler must fit in U256"); + assert!( + self.log2_input_span < 64 && self.log2_barch_span < 64 && self.log2_uarch_span < 64, + "position fields must fit in u64" + ); + } + + /// Splits a flat ruler position into the paper's (input, big, + /// ustep) coordinates. Valid for transition positions, which are + /// strictly inside the ruler. + pub fn decompose(&self, position: U256) -> Position { + assert!(position < self.ruler_span(), "past the epoch's end"); + let field = |shift: u64, bits: u64| -> u64 { + u64::try_from((position >> shift) & ((U256::from(1) << bits) - U256::from(1))) + .expect("field bounded by its span") + }; + Position { + input: field(self.log2_window_span(), self.log2_input_span), + big: field(self.log2_uarch_span, self.log2_barch_span), + ustep: field(0, self.log2_uarch_span), + } + } + + /// The inverse of [`Structure::decompose`]. + pub fn compose(&self, p: Position) -> U256 { + assert!(p.input < self.max_inputs(), "input field out of range"); + assert!( + p.big < (1u64 << self.log2_barch_span), + "big field out of range" + ); + assert!(p.ustep < self.big_span(), "ustep field out of range"); + (U256::from(p.input) << self.log2_window_span()) + | (U256::from(p.big) << self.log2_uarch_span) + | U256::from(p.ustep) + } + + /// The flat position where input window `w` begins. + pub fn window_start(&self, w: u64) -> U256 { + U256::from(w) << self.log2_window_span() + } +} + +/// A ruler position in the paper's (c, b, a) coordinates: which input +/// window, which big cycle within it, which uarch slot within that. +/// The single authority for the meta-cycle field layout; pack and +/// unpack through [`Structure::compose`] / [`Structure::decompose`] +/// only at the chain boundary and quartet math. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Position { + pub input: u64, + pub big: u64, + pub ustep: u64, +} + +impl Position { + /// The window-opening slot: the fused transition (checkpoint plus + /// input delivery plus the first ustep) when the window feeds. + pub fn is_window_start(&self) -> bool { + self.big == 0 && self.ustep == 0 + } + + /// A big-cycle boundary: the uarch is pristine here. + pub fn is_big_start(&self) -> bool { + self.ustep == 0 + } + + /// The big cycle's closing slot: the final (possibly identity) + /// ustep, the ureset, and the revert check. Kept as the span's + /// last uarch index rather than an explicit Reset variant: the + /// slot always fuses the three operations, so a separate + /// representation state would never change behavior (explored per + /// the workstream-4 note; the spec tests hold either way). + pub fn is_closing_slot(&self, structure: &Structure) -> bool { + self.ustep == structure.big_span() - 1 + } +} + +/// An input window boundary: the position [`Structure::window_start`] +/// of its index, where the open regime stores machines (yielded at an +/// input boundary, pristine uarch - asserted at store and resume). +/// The snapshot seam speaks boundaries end to end, so the conversion +/// to a flat ruler position happens in exactly one place (the machine +/// factory) instead of shift arithmetic at every module seam. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct InputBoundary(pub u64); + +impl InputBoundary { + pub fn position(&self, structure: &Structure) -> U256 { + structure.window_start(self.0) + } +} + +/// The identifier of a computation-hash node: every merkle node of +/// every commitment tree over the epoch is one quartet. A level root +/// (shift 0, full height for its stride) identifies a computation hash +/// itself; bisection children and proof siblings are the general case. +/// +/// The node has 2^height sampled leaves; sampled leaf j is the ruler +/// leaf at position (j + 1) * 2^log2_stride - 1, i.e. the post-state +/// after each full stride. The node covers ruler positions +/// [shift * 2^(height + log2_stride), (shift + 1) * 2^(height + log2_stride)). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Quartet { + pub epoch: u64, + pub log2_stride: u64, + pub height: u64, + pub shift: U256, +} + +impl Quartet { + /// The root of a whole tournament level's commitment tree. + pub fn level_root(epoch: u64, log2_stride: u64, height: u64) -> Self { + Quartet { + epoch, + log2_stride, + height, + shift: U256::ZERO, + } + } + + pub fn assert_valid(&self, structure: &Structure) { + let total = structure.log2_ruler_span(); + assert!( + self.log2_stride + self.height <= total, + "quartet exceeds the ruler: stride {} + height {} > {}", + self.log2_stride, + self.height, + total + ); + let log2_max_shift = total - self.log2_stride - self.height; + assert!( + self.shift < (U256::from(1) << log2_max_shift), + "shift out of range" + ); + } + + /// First ruler position covered (a transition count, not a leaf). + pub fn span_start(&self) -> U256 { + self.shift << (self.height + self.log2_stride) + } + + /// One past the last ruler position covered. + pub fn span_end(&self) -> U256 { + (self.shift + U256::from(1)) << (self.height + self.log2_stride) + } + + pub fn leaf_count(&self) -> U256 { + U256::from(1) << self.height + } + + /// The two children, one height down. None at height 0 (a sampled + /// leaf has no children in this tree; finer detail lives at a + /// smaller stride, which is a different quartet subspace). + pub fn children(&self) -> Option<(Quartet, Quartet)> { + if self.height == 0 { + return None; + } + let left = Quartet { + epoch: self.epoch, + log2_stride: self.log2_stride, + height: self.height - 1, + shift: self.shift << 1, + }; + let right = Quartet { + shift: (self.shift << 1) + U256::from(1), + ..left.clone() + }; + Some((left, right)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn position_round_trips_and_names_the_slots() { + let s = Structure { + log2_input_span: 2, + log2_barch_span: 2, + log2_uarch_span: 3, + }; + s.assert_valid(); + + // The toy picture of docs/computation-hash.md: window span 32, + // big span 8. + let cases = [ + ( + 0u64, + Position { + input: 0, + big: 0, + ustep: 0, + }, + ), + ( + 7, + Position { + input: 0, + big: 0, + ustep: 7, + }, + ), + ( + 8, + Position { + input: 0, + big: 1, + ustep: 0, + }, + ), + ( + 31, + Position { + input: 0, + big: 3, + ustep: 7, + }, + ), + ( + 32, + Position { + input: 1, + big: 0, + ustep: 0, + }, + ), + ( + 127, + Position { + input: 3, + big: 3, + ustep: 7, + }, + ), + ]; + for (flat, expect) in cases { + let p = s.decompose(U256::from(flat)); + assert_eq!(p, expect); + assert_eq!(s.compose(p), U256::from(flat)); + } + + assert!(s.decompose(U256::ZERO).is_window_start()); + assert!(!s.decompose(U256::from(8)).is_window_start()); + assert!(s.decompose(U256::from(8)).is_big_start()); + assert!(s.decompose(U256::from(7)).is_closing_slot(&s)); + assert!(!s.decompose(U256::from(6)).is_closing_slot(&s)); + assert_eq!(s.window_start(3), U256::from(96)); + + // Production-shape spot check against the documented layout: + // input = meta >> 68, big = (meta >> 20) & (2^48 - 1), + // ustep = meta & (2^20 - 1). + let prod = Structure::PRODUCTION; + let meta = (U256::from(5u64) << 68) | (U256::from(77u64) << 20) | U256::from(9u64); + assert_eq!( + prod.decompose(meta), + Position { + input: 5, + big: 77, + ustep: 9 + } + ); + } + + #[test] + fn spans_and_children() { + let s = Structure { + log2_input_span: 2, + log2_barch_span: 2, + log2_uarch_span: 3, + }; + s.assert_valid(); + assert_eq!(s.log2_ruler_span(), 7); + assert_eq!(s.big_span(), 8); + assert_eq!(s.window_span(), U256::from(32)); + + let root = Quartet::level_root(0, 3, 4); + root.assert_valid(&s); + assert_eq!(root.span_start(), U256::ZERO); + assert_eq!(root.span_end(), U256::from(128)); + assert_eq!(root.leaf_count(), U256::from(16)); + + let (l, r) = root.children().unwrap(); + assert_eq!(l.span_end(), r.span_start()); + assert_eq!(l.span_start(), root.span_start()); + assert_eq!(r.span_end(), root.span_end()); + assert!(Quartet::level_root(0, 0, 0).children().is_none()); + } +} diff --git a/cartesi-rollups/node/epoch-manager/src/error.rs b/cartesi-rollups/node/src/epoch_manager/error.rs similarity index 84% rename from cartesi-rollups/node/epoch-manager/src/error.rs rename to cartesi-rollups/node/src/epoch_manager/error.rs index 16806b8a9..0731e5269 100644 --- a/cartesi-rollups/node/epoch-manager/src/error.rs +++ b/cartesi-rollups/node/src/epoch_manager/error.rs @@ -1,7 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -use cartesi_prt_core::strategy::error::ReactError; +use crate::hero::error::ReactError; use alloy::contract::Error as AlloyContractError; use thiserror::Error; @@ -23,7 +23,7 @@ pub enum EpochManagerError { #[error(transparent)] StateManagerError { #[from] - source: rollups_state_manager::StateAccessError, + source: crate::storage::StorageError, }, } diff --git a/cartesi-rollups/node/src/epoch_manager/mod.rs b/cartesi-rollups/node/src/epoch_manager/mod.rs new file mode 100644 index 000000000..e5c0e36fc --- /dev/null +++ b/cartesi-rollups/node/src/epoch_manager/mod.rs @@ -0,0 +1,346 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +mod error; + +use self::error::Result; +use alloy::primitives::{Address, B256}; +use alloy::providers::DynProvider; +use log::{debug, info, trace}; +use std::{sync::Arc, time::Duration}; + +use crate::chain::Chain; +use crate::storage::{Epoch, Proof, Storage}; +use crate::sync::ShutdownSignal; +use crate::{ + hero::Hero, + tournament::{ArenaSender, allow_revert_rethrow_others}, +}; +use cartesi_dave_contracts::dave_consensus::DaveConsensus; + +pub struct EpochManager { + arena_sender: Arc, + consensus: Address, + signer_address: Address, + sleep_duration: Duration, + storage: Storage, + epoch_hero: (Option>, u64), +} + +impl EpochManager { + pub fn new( + arena_sender: Arc, + consensus_address: Address, + signer_address: Address, + storage: Storage, + sleep_duration: Duration, + ) -> Self { + Self { + arena_sender, + consensus: consensus_address, + signer_address, + sleep_duration, + storage, + epoch_hero: (None, 0), + } + } + + pub async fn execution_loop(mut self, shutdown: ShutdownSignal, chain: Chain) -> Result<()> { + let dave_consensus = DaveConsensus::new(self.consensus, chain.provider().clone()); + + // A failed iteration is retried, not fatal: every tick is + // re-derived from storage and chain, so transient provider + // errors (an RPC hiccup, a pinned read landing on a block the + // gateway no longer serves) cost one polling interval, never + // the validator. A BlockOutOfRangeError here killed the node + // mid-dispute on 2026-07-10 and its clocks kept running. + // Consensus violations stay fatal: they are asserts, not + // errors. + loop { + if let Err(e) = self.try_settle_epoch(&dave_consensus).await { + log::warn!("settle attempt failed, retrying next tick: {e}"); + } + if let Err(e) = self.try_react_epoch(&chain).await { + log::warn!("dispute tick failed, retrying next tick: {e}"); + } + + tokio::select! { biased; + _ = shutdown.requested() => break Ok(()), + _ = tokio::time::sleep(self.sleep_duration) => {} + } + } + } + + /// Drives the staged settlement protocol forward: a sentry claim + /// when this signer is a sentry, then staging the finished + /// tournament's result, then accepting it once every sentry + /// agrees or the claim staging period elapses. Each step is + /// guarded and idempotent, so one tick can advance whichever + /// step the chain is ready for. + pub async fn try_settle_epoch( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + self.try_submit_sentry_claim(dave_consensus).await?; + self.try_stage_tournament_result(dave_consensus).await?; + self.try_accept_tournament_result(dave_consensus).await?; + Ok(()) + } + + /// A sentry claims the post-epoch state it computed itself - + /// never the staged value - so claims stay an independent check + /// on the tournament result. + async fn try_submit_sentry_claim( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let sentry_id = dave_consensus + .getSentryId(self.signer_address) + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if sentry_id.is_zero() { + trace!( + "signer {} is not a sentry of DaveConsensus@{}", + self.signer_address, + dave_consensus.address() + ); + return Ok(()); + } + + let current_sealed_epoch = dave_consensus + .getCurrentSealedEpoch() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + let epoch_number = current_sealed_epoch.epochNumber; + + let has_claimed = dave_consensus + .hasSentryClaimedInEpoch(epoch_number, sentry_id) + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if has_claimed { + trace!( + "sentry {} (id {}) has already claimed for epoch {}", + self.signer_address, sentry_id, epoch_number + ); + return Ok(()); + } + + let can_accept = dave_consensus + .canAcceptStagedTournamentResult() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if can_accept.isTournamentResultStaged && can_accept.isClaimStagingPeriodOver { + trace!( + "epoch {} already has a staged result past its staging period; a claim buys nothing", + epoch_number + ); + return Ok(()); + } + + match self.storage.settlement_info( + u64::try_from(epoch_number).expect("fail to convert epoch number to u64"), + )? { + Some(settlement) => { + let claim = vec_u8_to_bytes_32(settlement.final_state.into()); + info!("submit sentry claim {} for epoch {}", claim, epoch_number); + let tx_result = dave_consensus + .submitSentryClaim(epoch_number, claim) + .send() + .await; + allow_revert_rethrow_others("submitSentryClaim", tx_result).await?; + } + None => { + trace!("wait for the `machine-runner` to insert the value"); + } + } + Ok(()) + } + + async fn try_stage_tournament_result( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let can_stage = dave_consensus + .canStageTournamentResult() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if !can_stage.isFinished || can_stage.isTournamentResultStaged { + trace!("tournament result not ready to be staged"); + return Ok(()); + } + + match self.storage.settlement_info( + u64::try_from(can_stage.epochNumber).expect("fail to convert epoch number to u64"), + )? { + Some(settlement) => { + assert_eq!( + settlement.computation_hash.data(), + can_stage.winnerCommitment, + "Winner commitment mismatch, notify all users!" + ); + assert_eq!( + vec_u8_to_bytes_32(settlement.final_state.into()), + can_stage.winnerPostEpochMachineStateHash, + "Winner final state mismatch, notify all users!" + ); + info!( + "stage tournament result of epoch {} with claim {}", + can_stage.epochNumber, + settlement.computation_hash.to_hex() + ); + let tx_result = dave_consensus + .stageTournamentResult( + can_stage.epochNumber, + vec_u8_to_bytes_32(settlement.output_merkle.into()), + to_bytes_32_vec(settlement.output_proof), + ) + .send() + .await; + allow_revert_rethrow_others("stageTournamentResult", tx_result).await?; + } + None => { + trace!("wait for the `machine-runner` to insert the value"); + } + } + Ok(()) + } + + async fn try_accept_tournament_result( + &mut self, + dave_consensus: &DaveConsensus::DaveConsensusInstance< + DynProvider, + alloy::network::Ethereum, + >, + ) -> Result<()> { + let can_accept = dave_consensus + .canAcceptStagedTournamentResult() + .block(alloy::eips::BlockId::pending()) + .call() + .await?; + + if !can_accept.isTournamentResultStaged { + trace!("staged tournament result not ready to be accepted"); + return Ok(()); + } + + match self.storage.settlement_info( + u64::try_from(can_accept.epochNumber).expect("fail to convert epoch number to u64"), + )? { + Some(settlement) => { + assert_eq!( + vec_u8_to_bytes_32(settlement.final_state.into()), + can_accept.stagedPostEpochMachineStateHash, + "Staged final state mismatch, notify all users!" + ); + assert_eq!( + vec_u8_to_bytes_32(settlement.output_merkle.into()), + can_accept.stagedPostEpochOutputsMerkleRoot, + "Staged outputs Merkle root mismatch, notify all users!" + ); + if can_accept.doAllSentriesAgreeWithStagedTournamentResult + || can_accept.isClaimStagingPeriodOver + { + info!( + "settle epoch {}: accept staged tournament result", + can_accept.epochNumber + ); + let tx_result = dave_consensus + .acceptStagedTournamentResult(can_accept.epochNumber) + .send() + .await; + allow_revert_rethrow_others("acceptStagedTournamentResult", tx_result).await?; + } + } + None => { + trace!("wait for the `machine-runner` to insert the value"); + } + } + Ok(()) + } + + async fn try_react_epoch(&mut self, chain: &Chain) -> Result<()> { + // participate in last sealed epoch tournament + if let Some(last_sealed_epoch) = self.storage.last_sealed_epoch()? { + match self + .storage + .settlement_info(last_sealed_epoch.epoch_number)? + { + Some(_) => { + trace!( + "dispute tournaments for epoch {}", + last_sealed_epoch.epoch_number + ); + self.react_dispute(chain, &last_sealed_epoch).await? + } + None => { + debug!( + "wait for `machine-runner` to insert settlement values for epoch {}", + last_sealed_epoch.epoch_number + ); + } + } + } + Ok(()) + } + + async fn react_dispute(&mut self, chain: &Chain, last_sealed_epoch: &Epoch) -> Result<()> { + self.get_latest_hero(last_sealed_epoch, chain)?; + self.epoch_hero + .0 + .as_mut() + .expect("hero should be instantiated") + .tick() + .await?; + + Ok(()) + } + + fn get_latest_hero(&mut self, last_sealed_epoch: &Epoch, chain: &Chain) -> Result<()> { + // either the hero has never been instantiated, or the sealed epoch has advanced + // we need to instantiate new epoch hero with appropriate data + if self.epoch_hero.0.is_none() || self.epoch_hero.1 != last_sealed_epoch.epoch_number { + // The hero reads the closed epoch's working set through + // its own storage handle (one connection per thread). + let storage = Storage::new(self.storage.state_dir())?; + + let hero = Hero::new( + self.arena_sender.clone(), + chain.clone(), + last_sealed_epoch.root_tournament, + last_sealed_epoch.block_created_number, + storage, + last_sealed_epoch.epoch_number, + )?; + + self.epoch_hero = (Some(hero), last_sealed_epoch.epoch_number); + } + + Ok(()) + } +} + +fn to_bytes_32_vec(proof: Proof) -> Vec { + proof.inner().iter().map(B256::from).collect() +} + +fn vec_u8_to_bytes_32(hash: Vec) -> B256 { + B256::from_slice(&hash) +} diff --git a/prt/client-rs/core/src/strategy/error.rs b/cartesi-rollups/node/src/hero/error.rs similarity index 67% rename from prt/client-rs/core/src/strategy/error.rs rename to cartesi-rollups/node/src/hero/error.rs index e348a880d..482ee4f43 100644 --- a/prt/client-rs/core/src/strategy/error.rs +++ b/cartesi-rollups/node/src/hero/error.rs @@ -1,6 +1,5 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -use crate::{db::sql::error::DisputeStateAccessError, machine::error::MachineInstanceError}; use alloy::contract::Error as AlloyContractError; use anyhow::Error as AnyhowError; use thiserror::Error; @@ -8,15 +7,9 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum ReactError { #[error(transparent)] - MachineInstance { + Storage { #[from] - source: MachineInstanceError, - }, - - #[error(transparent)] - DisputeStateAccessError { - #[from] - source: DisputeStateAccessError, + source: crate::storage::StorageError, }, #[error(transparent)] diff --git a/cartesi-rollups/node/src/hero/gc.rs b/cartesi-rollups/node/src/hero/gc.rs new file mode 100644 index 000000000..c6b9bb57e --- /dev/null +++ b/cartesi-rollups/node/src/hero/gc.rs @@ -0,0 +1,84 @@ +//! The bond sweeper: eliminates matches both of whose commitments ran +//! out of clock, and inner tournaments the chain marks eliminable. +//! Pure housekeeping - the Hero wins disputes; the sweep frees bonds. + +use ::log::debug; +use alloy::primitives::Address; +use async_recursion::async_recursion; +use std::sync::Arc; + +use crate::hero::error::Result; +use crate::tournament::{ArenaSender, DisputeState}; + +pub struct GarbageCollector { + arena_sender: Arc, + root_tournament: Address, +} + +impl GarbageCollector { + pub fn new(arena_sender: Arc, root_tournament: Address) -> Self { + Self { + arena_sender, + root_tournament, + } + } + + pub async fn tick(&self, dispute: &DisputeState) -> Result<()> { + self.react_tournament(self.root_tournament, dispute).await + } + + #[async_recursion] + async fn react_tournament<'a>( + &self, + tournament_address: Address, + dispute: &DisputeState, + ) -> Result<()> { + let (tournament, overlay) = dispute + .tournament(&tournament_address) + .expect("the sweep only descends into reachable tournaments"); + + for m in tournament.live_matches() { + // An eliminable inner tournament dies wholesale; otherwise + // sweep inside it first, innermost eliminations leading. + if let Some(inner_address) = m.inner_tournament { + let (inner, inner_overlay) = dispute + .tournament(&inner_address) + .expect("a live sealed match's inner tournament is reachable"); + + if inner_overlay.can_be_eliminated { + debug!( + "eliminate inner tournament {inner_address} of level {}, child of tournament {tournament_address}", + inner.level + ); + self.arena_sender + .eliminate_inner_tournament(tournament_address, inner_address) + .await?; + } else { + self.react_tournament(inner_address, dispute).await?; + } + } + + let clock_one = overlay + .clocks + .get(&m.id.commitment_one) + .expect("every joined commitment carries a clock"); + let clock_two = overlay + .clocks + .get(&m.id.commitment_two) + .expect("every joined commitment carries a clock"); + if (!clock_one.has_time() && (clock_one.time_since_timeout() > clock_two.allowance)) + || (!clock_two.has_time() && (clock_two.time_since_timeout() > clock_one.allowance)) + { + debug!( + "eliminate match for commitment {} and {} at tournament {} of level {}", + m.id.commitment_one, m.id.commitment_two, tournament_address, tournament.level + ); + + self.arena_sender + .eliminate_match(tournament_address, m.id) + .await?; + } + } + Ok(()) + } +} diff --git a/cartesi-rollups/node/src/hero/mod.rs b/cartesi-rollups/node/src/hero/mod.rs new file mode 100644 index 000000000..06e5d4a69 --- /dev/null +++ b/cartesi-rollups/node/src/hero/mod.rs @@ -0,0 +1,1269 @@ +//! The Hero (the paper's name for the honest validator) fights the +//! dispute: every tree node its tick needs - level roots, bisection +//! children, seal and join proofs - is a quartet query against the +//! [`DisputeSource`], and the quartet cache in the node database is +//! the restartable dispute state. Turn taking is positional: the Match +//! contract's runningLeafPosition is the leftmost leaf of the contested +//! node, so the node to open is a coordinate computation, not a tree +//! search. Transition proofs (win_leaf_match) ride the ruler's proving +//! verbs. [`gc::GarbageCollector`] sweeps timed-out matches and dead +//! inner tournaments. + +pub mod error; +pub mod gc; + +use std::sync::Arc; + +use crate::hero::error::Result; +use ::log::{debug, error, info}; +use alloy::primitives::{Address, U256}; +use async_recursion::async_recursion; + +use crate::merkle::{Digest, MerkleProof}; +use crate::{ + chain::Chain, + engine::{DisputeSource, LevelCoords, Positioner, Quartet, RulerFactory, stf::ProvingStf}, + hero::gc::GarbageCollector, + storage::Storage, + tournament::{ + ArenaSender, DisputeState, MatchLive, StateReader, TournamentOverlay, TournamentWinner, + fold::{MatchFold, TournamentFold}, + }, +}; + +#[derive(Debug, PartialEq)] +pub enum TournamentResult { + Lost, + Running, + Won, +} + +/// One tournament level's commitment, as the Hero sees it: where +/// the tree sits, its root, and the state its first leaf builds on +/// (the implicit hash). +#[derive(Debug, Clone)] +struct LevelCommitment { + coords: LevelCoords, + root: Digest, + initial_hash: Digest, +} + +/// Generic over the ruler factory so the react loop runs under the +/// toy in unit tests; production is the default parameter, and only +/// the engine (DisputeSource::on_store) knows how to assemble itself +/// from storage. +pub struct Hero { + arena_sender: Arc, + source: DisputeSource, + epoch: u64, + /// Hash of the epoch's initial snapshot: the root level's implicit + /// hash, and the anchor the root tournament was deployed with. + epoch_initial_hash: Digest, + root_tournament: Address, + reader: StateReader, + gc: GarbageCollector, +} + +impl Hero { + pub fn new( + arena_sender: Arc, + chain: Chain, + root_tournament: Address, + block_created_number: u64, + mut storage: Storage, + epoch_number: u64, + ) -> Result { + let work_dir = storage.epoch_directory(epoch_number)?; + + // The epoch start's row hash IS the machine's root hash (the + // CAS key): the root level's implicit hash, and the anchor + // the root tournament was deployed with. + let epoch_initial_hash: Digest = Digest::from_digest( + &storage + .snapshot_hash(epoch_number, 0)? + .expect("snapshot is inserted atomically with settlement info"), + ) + .map_err(anyhow::Error::from)?; + + // The reader persists finalized tournament events (fold phase + // 2) through its own connection to the shared database, like + // every other writer role. + let reader_storage = Storage::new(storage.state_dir())?; + + // One facade serves both tree material and machine + // positioning (proof witnesses, nested-tournament entry); it + // assembles its whole working set from storage. + let source = DisputeSource::on_store(storage, epoch_number, work_dir.join("engine"))?; + + let reader = StateReader::new(chain, block_created_number, reader_storage)?; + let gc = GarbageCollector::new(arena_sender.clone(), root_tournament); + Ok(Self { + arena_sender, + source, + epoch: epoch_number, + epoch_initial_hash, + root_tournament, + reader, + gc, + }) + } +} + +impl Hero +where + F::S: ProvingStf, +{ + pub async fn tick(&mut self) -> Result { + let dispute = self.reader.fetch_from_root(self.root_tournament).await?; + + self.gc.tick(&dispute).await?; + self.react_tournament( + None, + self.epoch_initial_hash, + self.root_tournament, + &dispute, + ) + .await + } + + /// This level's commitment: root from the source, coordinates from + /// the tournament's overlay, implicit hash from the caller (the + /// epoch's initial state at the root, the sealed agree state for + /// inners). + fn level_commitment( + &mut self, + overlay: &TournamentOverlay, + initial_hash: Digest, + ) -> Result { + let coords = LevelCoords::new( + self.epoch, + overlay.base_cycle, + overlay.log2_stride, + overlay.log2_stride_count, + ); + let root = self.source.node(&coords.root())?; + Ok(LevelCommitment { + coords, + root, + initial_hash, + }) + } + + #[async_recursion] + async fn react_tournament<'a>( + &mut self, + parent: Option<&'a LevelCommitment>, + initial_hash: Digest, + tournament_address: Address, + dispute: &DisputeState, + ) -> Result { + info!("Enter tournament at address: {}", tournament_address); + let (tournament, overlay) = dispute + .tournament(&tournament_address) + .expect("the hero only descends into reachable tournaments"); + + let commitment = self.level_commitment(overlay, initial_hash)?; + + if let Some(winner) = &overlay.winner { + match winner { + TournamentWinner::Root(winner_commitment, winner_state) => { + info!( + "tournament finished, winner commitment: {}, state hash: {}", + winner_commitment, winner_state, + ); + if commitment.root == *winner_commitment { + info!("hero won tournament {}", tournament.address); + return Ok(TournamentResult::Won); + } else { + error!("hero lost tournament {}", tournament.address); + return Ok(TournamentResult::Lost); + } + } + TournamentWinner::Inner(parent_commitment, _) => { + let parent = parent.expect("inner tournament without a parent level"); + if *parent_commitment != parent.root { + error!("hero lost tournament {}", tournament.address); + return Ok(TournamentResult::Lost); + } else { + info!( + "win tournament {} of level {} for commitment {}", + tournament.address, tournament.level, commitment.root, + ); + let (left, right) = self.source.children(&parent.coords.root())?; + let (parent_address, _) = tournament + .parent + .expect("inner tournament without a parent"); + self.arena_sender + .win_inner_match(parent_address, tournament.address, left, right) + .await?; + + return Ok(TournamentResult::Running); + } + } + } + } + + match tournament.commitments.get(&commitment.root) { + Some(ours) => { + let clock = overlay + .clocks + .get(&commitment.root) + .expect("every joined commitment carries a clock"); + info!("{}", clock); + + // The fold indexes all matches; a commitment fights at + // most one live match, so the latest one is either it + // or history. + let live_match = ours + .latest_match + .map(|i| &tournament.matches[i]) + .filter(|m| m.is_live()); + match live_match { + Some(m) => { + let live = overlay + .live_matches + .get(&m.id.hash()) + .expect("every live match carries an overlay"); + self.react_match(m, live, &commitment, tournament, overlay, dispute) + .await?; + } + None => info!("no match found for commitment: {}", commitment.root), + } + } + None => { + self.join_tournament_if_needed(tournament, &commitment) + .await?; + } + } + + Ok(TournamentResult::Running) + } + + async fn join_tournament_if_needed( + &mut self, + tournament: &TournamentFold, + commitment: &LevelCommitment, + ) -> Result<()> { + let (left, right) = self.source.children(&commitment.coords.root())?; + let proof_last = self.source.prove_last(&commitment.coords)?; + + info!( + "join tournament {} of level {} with commitment {}", + tournament.address, tournament.level, commitment.root, + ); + + // Get the bond value required for joining the tournament + let bond_value = self.arena_sender.bond_value(tournament.address).await?; + + self.arena_sender + .join_tournament(tournament.address, &proof_last, left, right, bond_value) + .await?; + + Ok(()) + } + + /// The node a running match contests, and whether it is ours to + /// open: the contract walks otherParent down one commitment tree, + /// and it is our turn exactly when the node at that position of + /// our tree is otherParent. + fn contested_node( + &mut self, + live: &MatchLive, + commitment: &LevelCommitment, + ) -> Result> { + let quartet = commitment + .coords + .node(live.current_height, live.running_leaf_position); + if self.source.node(&quartet)? == live.other_parent { + Ok(Some(quartet)) + } else { + Ok(None) + } + } + + #[async_recursion] + async fn react_match<'a>( + &mut self, + match_fold: &'a MatchFold, + live: &'a MatchLive, + commitment: &'a LevelCommitment, + tournament: &'a TournamentFold, + overlay: &'a TournamentOverlay, + dispute: &DisputeState, + ) -> Result<()> { + info!("Enter match at HEIGHT: {}", live.current_height); + + self.win_timeout_match(match_fold, commitment, tournament, overlay) + .await?; + + if live.current_height == 0 { + self.react_sealed_match(match_fold, live, commitment, tournament, overlay, dispute) + .await?; + } else if live.current_height == 1 { + self.react_unsealed_match(match_fold, live, commitment, tournament, overlay) + .await?; + } else { + self.react_running_match(match_fold, live, commitment, tournament) + .await?; + } + Ok(()) + } + + async fn win_timeout_match( + &mut self, + match_fold: &MatchFold, + commitment: &LevelCommitment, + tournament: &TournamentFold, + overlay: &TournamentOverlay, + ) -> Result<()> { + let opponent = if commitment.root == match_fold.id.commitment_one { + match_fold.id.commitment_two + } else { + match_fold.id.commitment_one + }; + let opponent_clock = overlay + .clocks + .get(&opponent) + .expect("every joined commitment carries a clock"); + + if !opponent_clock.has_time() { + let (left, right) = self.source.children(&commitment.coords.root())?; + + info!( + "win match by timeout in tournament {} of level {} for commitment {}", + tournament.address, tournament.level, commitment.root, + ); + + self.arena_sender + .win_timeout_match(tournament.address, match_fold.id, left, right) + .await?; + } + Ok(()) + } + + #[async_recursion] + async fn react_sealed_match<'a>( + &mut self, + match_fold: &'a MatchFold, + live: &'a MatchLive, + commitment: &'a LevelCommitment, + tournament: &'a TournamentFold, + overlay: &'a TournamentOverlay, + dispute: &DisputeState, + ) -> Result<()> { + if tournament.level == (overlay.max_level - 1) { + let (left, right) = self.source.children(&commitment.coords.root())?; + + let proof = { + // Position on the disputed leaf (a snapshot resume + // plus advance), check the chain-anchored agree + // state, and prove the one transition. + let mut ruler = self.source.machine_at(live.leaf_cycle)?; + assert_eq!( + ruler.state_hash()?, + live.other_parent, + "positioned machine diverges from the on-chain agree state" + ); + ruler.prove_transition()? + }; + + info!( + "win leaf match in tournament {} of level {} for commitment {}, proof size {}", + tournament.address, + tournament.level, + commitment.root, + proof.0.len() + ); + self.arena_sender + .win_leaf_match(tournament.address, match_fold.id, left, right, proof.0) + .await?; + } else { + // The sealed match's otherParent is the agreed state the + // inner level builds on: its implicit hash, chain-anchored. + self.react_tournament( + Some(commitment), + live.other_parent, + match_fold + .inner_tournament + .expect("sealed inner match without its tournament"), + dispute, + ) + .await?; + } + + Ok(()) + } + + async fn react_unsealed_match( + &mut self, + match_fold: &MatchFold, + live: &MatchLive, + commitment: &LevelCommitment, + tournament: &TournamentFold, + overlay: &TournamentOverlay, + ) -> Result<()> { + let Some(contested) = self.contested_node(live, commitment)? else { + debug!("not my turn to react"); + return Ok(()); + }; + let (left, right) = self.source.children(&contested)?; + + let running_leaf_position = { + if left != live.left_node { + // disagree on left + live.running_leaf_position + } else { + // disagree on right + live.running_leaf_position + U256::ONE + } + }; + + let agree_state_proof = if running_leaf_position.is_zero() { + MerkleProof::leaf(commitment.initial_hash, U256::ZERO) + } else { + self.source + .prove_leaf(&commitment.coords, running_leaf_position - U256::ONE)? + }; + + if tournament.level == (overlay.max_level - 1) { + info!( + "seal leaf match in tournament {} of level {} for commitment {}", + tournament.address, tournament.level, commitment.root, + ); + self.arena_sender + .seal_leaf_match( + tournament.address, + match_fold.id, + left, + right, + &agree_state_proof, + ) + .await?; + } else { + info!( + "seal inner match in tournament {} of level {} for commitment {}", + tournament.address, tournament.level, commitment.root, + ); + self.arena_sender + .seal_inner_match( + tournament.address, + match_fold.id, + left, + right, + &agree_state_proof, + ) + .await?; + } + Ok(()) + } + + async fn react_running_match( + &mut self, + match_fold: &MatchFold, + live: &MatchLive, + commitment: &LevelCommitment, + tournament: &TournamentFold, + ) -> Result<()> { + let Some(contested) = self.contested_node(live, commitment)? else { + debug!("not my turn to react"); + return Ok(()); + }; + let (left, right) = self.source.children(&contested)?; + let (left_child, right_child) = contested.children().expect("running match above leaves"); + + let (new_left, new_right) = if left != live.left_node { + debug!("going down to the left"); + self.source.children(&left_child)? + } else { + debug!("going down to the right"); + self.source.children(&right_child)? + }; + + info!( + "advance match with current height {} in tournament {} of level {} for commitment {}", + live.current_height, tournament.address, tournament.level, commitment.root, + ); + self.arena_sender + .advance_match( + tournament.address, + match_fold.id, + left, + right, + new_left, + new_right, + ) + .await?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + //! The Hero's decision table, unit-tested chain-free: hand-built + //! DisputeStates (the fold fed synthetic events, the overlay + //! written directly) over the toy engine source, with a recording + //! arena in place of the chain. Every arena verb the react loop + //! can choose is pinned here; the e2e suites remain the outer net + //! that checks the chain agrees with these choices. + + use super::*; + use crate::engine::spec::{S_SMALL, toy_source}; + use crate::engine::{ToyFactory, ToyInput, ToyOutcome, ToyStf}; + use crate::merkle::MerkleProof; + use crate::tournament::fold::{EventKind, Fold, TournamentEvent}; + use crate::tournament::{ClockState, MachineProof, MatchID}; + use alloy::providers::{Provider, ProviderBuilder}; + use async_trait::async_trait; + use std::collections::HashMap; + + fn addr(byte: u8) -> Address { + Address::from([byte; 20]) + } + + fn dg(byte: u8) -> Digest { + Digest::from_digest(&[byte; 32]).unwrap() + } + + /// Every arena verb the Hero can choose, with the arguments the + /// tests pin. + #[derive(Debug, Clone, PartialEq)] + enum ArenaCall { + Join { + tournament: Address, + left: Digest, + right: Digest, + }, + Advance { + tournament: Address, + id: Digest, + left: Digest, + right: Digest, + new_left: Digest, + new_right: Digest, + }, + SealInner { + tournament: Address, + id: Digest, + agree_position: U256, + }, + SealLeaf { + tournament: Address, + id: Digest, + agree_position: U256, + }, + WinInner { + tournament: Address, + child: Address, + }, + WinTimeout { + tournament: Address, + id: Digest, + }, + WinLeaf { + tournament: Address, + id: Digest, + proof: Vec, + }, + EliminateMatch { + tournament: Address, + id: Digest, + }, + EliminateInner { + tournament: Address, + child: Address, + }, + } + + #[derive(Default)] + struct RecordingArena { + calls: std::sync::Mutex>, + } + + impl RecordingArena { + fn push(&self, call: ArenaCall) { + self.calls.lock().unwrap().push(call); + } + + fn take(&self) -> Vec { + std::mem::take(&mut *self.calls.lock().unwrap()) + } + } + + #[async_trait] + impl ArenaSender for RecordingArena { + async fn join_tournament( + &self, + tournament: Address, + _proof: &MerkleProof, + left: Digest, + right: Digest, + _bond_value: U256, + ) -> Result<()> { + self.push(ArenaCall::Join { + tournament, + left, + right, + }); + Ok(()) + } + + async fn advance_match( + &self, + tournament: Address, + match_id: MatchID, + left: Digest, + right: Digest, + new_left: Digest, + new_right: Digest, + ) -> Result<()> { + self.push(ArenaCall::Advance { + tournament, + id: match_id.hash(), + left, + right, + new_left, + new_right, + }); + Ok(()) + } + + async fn seal_inner_match( + &self, + tournament: Address, + match_id: MatchID, + _left: Digest, + _right: Digest, + agree_proof: &MerkleProof, + ) -> Result<()> { + self.push(ArenaCall::SealInner { + tournament, + id: match_id.hash(), + agree_position: agree_proof.position, + }); + Ok(()) + } + + async fn win_inner_match( + &self, + tournament: Address, + child: Address, + _left: Digest, + _right: Digest, + ) -> Result<()> { + self.push(ArenaCall::WinInner { tournament, child }); + Ok(()) + } + + async fn win_timeout_match( + &self, + tournament: Address, + match_id: MatchID, + _left: Digest, + _right: Digest, + ) -> Result<()> { + self.push(ArenaCall::WinTimeout { + tournament, + id: match_id.hash(), + }); + Ok(()) + } + + async fn seal_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + _left: Digest, + _right: Digest, + agree_proof: &MerkleProof, + ) -> Result<()> { + self.push(ArenaCall::SealLeaf { + tournament, + id: match_id.hash(), + agree_position: agree_proof.position, + }); + Ok(()) + } + + async fn win_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + _left: Digest, + _right: Digest, + proof: MachineProof, + ) -> Result<()> { + self.push(ArenaCall::WinLeaf { + tournament, + id: match_id.hash(), + proof, + }); + Ok(()) + } + + async fn eliminate_match(&self, tournament: Address, match_id: MatchID) -> Result<()> { + self.push(ArenaCall::EliminateMatch { + tournament, + id: match_id.hash(), + }); + Ok(()) + } + + async fn eliminate_inner_tournament( + &self, + tournament: Address, + child: Address, + ) -> Result<()> { + self.push(ArenaCall::EliminateInner { tournament, child }); + Ok(()) + } + + async fn bond_value(&self, _tournament: Address) -> Result { + Ok(U256::from(7)) + } + } + + const ROOT: fn() -> Address = || addr(0xA1); + const INNER: fn() -> Address = || addr(0xB2); + + fn script() -> Vec { + vec![ToyInput { + big_cycles: vec![2, 1], + outcome: ToyOutcome::Accept, + }] + } + + /// Two-level geometry over S_SMALL (ruler 2^7): the root at + /// stride 2^3 height 4, inners at stride 2^0 height 3. + const TWO_LEVEL: (u64, (u64, u64), (u64, u64)) = (2, (3, 4), (0, 3)); + /// One-level geometry: the root IS the leaf level, whole ruler at + /// uarch granularity. + const ONE_LEVEL: (u64, (u64, u64), (u64, u64)) = (1, (0, 7), (0, 0)); + + fn level0(geometry: (u64, (u64, u64), (u64, u64))) -> LevelCoords { + let (stride, height) = geometry.1; + LevelCoords::new(0, U256::ZERO, stride, height) + } + + fn overlay_for( + geometry: (u64, (u64, u64), (u64, u64)), + level: u64, + base_cycle: U256, + ) -> TournamentOverlay { + let (stride, height) = if level == 0 { geometry.1 } else { geometry.2 }; + TournamentOverlay { + max_level: geometry.0, + log2_stride: stride, + log2_stride_count: height, + base_cycle, + winner: None, + can_be_eliminated: false, + clocks: HashMap::new(), + live_matches: HashMap::new(), + } + } + + fn alive_clock() -> ClockState { + ClockState { + allowance: 100, + start_instant: 0, + block_number: 0, + } + } + + /// Timed out so long ago that even elimination's overshoot + /// condition holds against a 100-block allowance. + fn dead_clock() -> ClockState { + ClockState { + allowance: 5, + start_instant: 1, + block_number: 1000, + } + } + + fn ev(tournament: Address, kind: EventKind) -> TournamentEvent { + TournamentEvent { + tournament, + block: 1, + kind, + } + } + + fn joined(root: Digest) -> EventKind { + EventKind::CommitmentJoined { + root, + final_state: dg(0xFF), + } + } + + /// Expected node hashes come from an independent toy source: the + /// same script, a fresh cache. + fn ref_node(level: &LevelCoords, height: u64, position: U256) -> Digest { + let mut source = toy_source(S_SMALL, &script()); + source.node(&level.node(height, position)).unwrap() + } + + fn ref_children(level: &LevelCoords, height: u64, position: U256) -> (Digest, Digest) { + let mut source = toy_source(S_SMALL, &script()); + source.children(&level.node(height, position)).unwrap() + } + + fn toy_hero() -> (Hero, Arc) { + let arena = Arc::new(RecordingArena::default()); + let source = toy_source(S_SMALL, &script()); + // Never dialed: react_tournament takes the state as an + // argument; only tick() fetches (and only tick() would touch + // the reader's event-log storage). + let chain = crate::chain::Chain::new( + ProviderBuilder::new() + .connect_http("http://127.0.0.1:1".parse().unwrap()) + .erased(), + vec![], + ); + let reader = StateReader::new(chain, 0, crate::engine::spec::toy_storage(S_SMALL)).unwrap(); + let gc = GarbageCollector::new(arena.clone(), ROOT()); + let hero = Hero { + arena_sender: arena.clone(), + source, + epoch: 0, + epoch_initial_hash: ToyStf::hash_of(0), + root_tournament: ROOT(), + reader, + gc, + }; + (hero, arena) + } + + /// A root tournament where our commitment and a sybil's fight one + /// live match, positioned by the caller. Returns the dispute and + /// the match id hash. + fn dispute_with_match( + geometry: (u64, (u64, u64), (u64, u64)), + ours: Digest, + live: MatchLive, + ) -> (DisputeState, Digest) { + let sybil = dg(0x51); + let id = MatchID { + commitment_one: ours, + commitment_two: sybil, + }; + let id_hash = id.hash(); + + let mut fold = Fold::new(ROOT()); + fold.apply(&ev(ROOT(), joined(ours))).unwrap(); + fold.apply(&ev(ROOT(), joined(sybil))).unwrap(); + fold.apply(&ev( + ROOT(), + EventKind::MatchCreated { + one: ours, + two: sybil, + left_of_two: dg(0x52), + }, + )) + .unwrap(); + + let mut ov = overlay_for(geometry, 0, U256::ZERO); + ov.clocks.insert(ours, alive_clock()); + ov.clocks.insert(sybil, alive_clock()); + ov.live_matches.insert(id_hash, live); + + let mut overlay = HashMap::new(); + overlay.insert(ROOT(), ov); + (DisputeState { fold, overlay }, id_hash) + } + + fn running_match(other_parent: Digest, left_node: Digest, current_height: u64) -> MatchLive { + MatchLive { + other_parent, + left_node, + right_node: dg(0x53), + running_leaf_position: U256::ZERO, + current_height, + leaf_cycle: U256::ZERO, + } + } + + #[tokio::test] + async fn joins_a_tournament_it_has_not_joined() { + let (mut hero, arena) = toy_hero(); + let mut overlay = HashMap::new(); + overlay.insert(ROOT(), overlay_for(TWO_LEVEL, 0, U256::ZERO)); + let dispute = DisputeState { + fold: Fold::new(ROOT()), + overlay, + }; + + let result = hero + .react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!(result, TournamentResult::Running); + let level = level0(TWO_LEVEL); + let (left, right) = ref_children(&level, level.height, U256::ZERO); + assert_eq!( + arena.take(), + vec![ArenaCall::Join { + tournament: ROOT(), + left, + right + }] + ); + } + + #[tokio::test] + async fn reports_the_root_verdict() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + + for (winner, expected) in [ + (ours, TournamentResult::Won), + (dg(0x66), TournamentResult::Lost), + ] { + let (mut hero, arena) = toy_hero(); + let mut ov = overlay_for(TWO_LEVEL, 0, U256::ZERO); + ov.winner = Some(TournamentWinner::Root(winner, dg(0x09))); + let mut overlay = HashMap::new(); + overlay.insert(ROOT(), ov); + let dispute = DisputeState { + fold: Fold::new(ROOT()), + overlay, + }; + + let result = hero + .react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + assert_eq!(result, expected); + assert_eq!(arena.take(), vec![]); + } + } + + #[tokio::test] + async fn advances_when_it_is_our_turn() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + // The contract walked otherParent onto OUR node at height 2: + // our turn. Its left disagrees with ours, so we descend left. + let contested = ref_node(&level, 2, U256::ZERO); + let (mut hero, arena) = toy_hero(); + let (dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(contested, dg(0x66), 2)); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + let (left, right) = ref_children(&level, 2, U256::ZERO); + let (new_left, new_right) = ref_children(&level, 1, U256::ZERO); + assert_eq!( + arena.take(), + vec![ArenaCall::Advance { + tournament: ROOT(), + id: id_hash, + left, + right, + new_left, + new_right + }] + ); + } + + #[tokio::test] + async fn descends_right_when_agreeing_on_the_left() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let contested = ref_node(&level, 2, U256::ZERO); + let (left, right) = ref_children(&level, 2, U256::ZERO); + let (mut hero, arena) = toy_hero(); + // otherParent is ours and its left EQUALS ours: the + // disagreement is on the right child. + let (dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(contested, left, 2)); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + let (new_left, new_right) = ref_children(&level, 1, U256::from(2)); + assert_eq!( + arena.take(), + vec![ArenaCall::Advance { + tournament: ROOT(), + id: id_hash, + left, + right, + new_left, + new_right + }] + ); + } + + #[tokio::test] + async fn waits_when_it_is_not_our_turn() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (mut hero, arena) = toy_hero(); + // otherParent is not a node of our tree: the opponent moves. + let (dispute, _) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x99), dg(0x66), 2)); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!(arena.take(), vec![]); + } + + #[tokio::test] + async fn seals_an_inner_match_at_height_one() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let contested = ref_node(&level, 1, U256::ZERO); + let (mut hero, arena) = toy_hero(); + // Divergence at position zero: the agree state is the level's + // implicit hash, proved at position zero. + let (dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(contested, dg(0x66), 1)); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::SealInner { + tournament: ROOT(), + id: id_hash, + agree_position: U256::ZERO + }] + ); + } + + #[tokio::test] + async fn seals_a_leaf_match_on_the_last_level() { + let level = level0(ONE_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let contested = ref_node(&level, 1, U256::ZERO); + let (mut hero, arena) = toy_hero(); + let (dispute, id_hash) = + dispute_with_match(ONE_LEVEL, ours, running_match(contested, dg(0x66), 1)); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::SealLeaf { + tournament: ROOT(), + id: id_hash, + agree_position: U256::ZERO + }] + ); + } + + #[tokio::test] + async fn proves_the_leaf_transition_by_shape() { + // The three witness shapes prove_transition selects by + // position: window start (feed + fused ustep), plain ustep, + // and the closing slot (ustep + ureset + revert check). The + // toy's inert markers make the selection visible. + let cases: [(u64, &[u8]); 3] = [ + (0, b"toy-feed;toy-ustep;"), + (1, b"toy-ustep;"), + (7, b"toy-ustep;toy-ureset;toy-revert-check;"), + ]; + + for (position, expected_proof) in cases { + let level = level0(ONE_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + // The chain-anchored agree state at the disputed leaf: + // what the positioned prover must reproduce. + let agree = ToyFactory { + structure: S_SMALL, + script: script(), + } + .ruler_at(U256::from(position)) + .unwrap() + .state_hash() + .unwrap(); + + let (mut hero, arena) = toy_hero(); + let live = MatchLive { + other_parent: agree, + left_node: dg(0x66), + right_node: dg(0x53), + running_leaf_position: U256::ZERO, + current_height: 0, + leaf_cycle: U256::from(position), + }; + let (dispute, id_hash) = dispute_with_match(ONE_LEVEL, ours, live); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::WinLeaf { + tournament: ROOT(), + id: id_hash, + proof: expected_proof.to_vec() + }], + "shape at position {position}" + ); + } + } + + #[tokio::test] + async fn wins_by_timeout_when_the_opponent_clock_dies() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (mut hero, arena) = toy_hero(); + let (mut dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x99), dg(0x66), 2)); + dispute + .overlay + .get_mut(&ROOT()) + .unwrap() + .clocks + .insert(dg(0x51), dead_clock()); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::WinTimeout { + tournament: ROOT(), + id: id_hash + }] + ); + } + + #[tokio::test] + async fn descends_into_the_inner_tournament_and_wins_it() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (mut hero, arena) = toy_hero(); + // The parent match sealed into an inner tournament whose + // winner the chain already declared: our parent commitment. + let (mut dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x77), dg(0x66), 0)); + dispute + .fold + .apply(&ev( + ROOT(), + EventKind::NewInnerTournament { + match_id_hash: id_hash, + child: INNER(), + }, + )) + .unwrap(); + let mut inner_ov = overlay_for(TWO_LEVEL, 1, U256::ZERO); + inner_ov.winner = Some(TournamentWinner::Inner(ours, dg(0x08))); + dispute.overlay.insert(INNER(), inner_ov); + + let result = hero + .react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!(result, TournamentResult::Running); + assert_eq!( + arena.take(), + vec![ArenaCall::WinInner { + tournament: ROOT(), + child: INNER() + }] + ); + } + + #[tokio::test] + async fn idles_when_its_match_is_history() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (mut hero, arena) = toy_hero(); + let (mut dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x99), dg(0x66), 2)); + // The match resolved; the fold remembers it, nothing is live. + dispute + .fold + .apply(&ev( + ROOT(), + EventKind::MatchDeleted { + match_id_hash: id_hash, + reason: crate::tournament::fold::MatchDeletionReason::Timeout, + winner: crate::tournament::fold::WinnerCommitment::One, + }, + )) + .unwrap(); + dispute + .overlay + .get_mut(&ROOT()) + .unwrap() + .live_matches + .clear(); + + hero.react_tournament(None, ToyStf::hash_of(0), ROOT(), &dispute) + .await + .unwrap(); + + assert_eq!(arena.take(), vec![]); + } + + #[tokio::test] + async fn gc_eliminates_a_match_both_of_whose_clocks_died() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (hero, arena) = toy_hero(); + let (mut dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x99), dg(0x66), 2)); + let clocks = &mut dispute.overlay.get_mut(&ROOT()).unwrap().clocks; + clocks.insert(ours, dead_clock()); + clocks.insert(dg(0x51), dead_clock()); + + hero.gc.tick(&dispute).await.unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::EliminateMatch { + tournament: ROOT(), + id: id_hash + }] + ); + } + + #[tokio::test] + async fn gc_eliminates_an_eliminable_inner_tournament() { + let level = level0(TWO_LEVEL); + let ours = ref_node(&level, level.height, U256::ZERO); + let (hero, arena) = toy_hero(); + let (mut dispute, id_hash) = + dispute_with_match(TWO_LEVEL, ours, running_match(dg(0x77), dg(0x66), 0)); + dispute + .fold + .apply(&ev( + ROOT(), + EventKind::NewInnerTournament { + match_id_hash: id_hash, + child: INNER(), + }, + )) + .unwrap(); + let mut inner_ov = overlay_for(TWO_LEVEL, 1, U256::ZERO); + inner_ov.can_be_eliminated = true; + dispute.overlay.insert(INNER(), inner_ov); + + hero.gc.tick(&dispute).await.unwrap(); + + assert_eq!( + arena.take(), + vec![ArenaCall::EliminateInner { + tournament: ROOT(), + child: INNER() + }] + ); + } +} diff --git a/common-rs/kms/src/lib.rs b/cartesi-rollups/node/src/kms.rs similarity index 99% rename from common-rs/kms/src/lib.rs rename to cartesi-rollups/node/src/kms.rs index 2d0e95b53..44aeddf69 100644 --- a/common-rs/kms/src/lib.rs +++ b/cartesi-rollups/node/src/kms.rs @@ -90,7 +90,7 @@ impl KmsSignerBuilder { } #[cfg(test)] -mod kms { +mod tests { use std::{ future::Future, panic::{UnwindSafe, catch_unwind}, diff --git a/cartesi-rollups/node/src/lib.rs b/cartesi-rollups/node/src/lib.rs new file mode 100644 index 000000000..615e4bf9b --- /dev/null +++ b/cartesi-rollups/node/src/lib.rs @@ -0,0 +1,179 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pub mod args; +pub mod blockchain_reader; +pub mod chain; +pub mod epoch_manager; +pub mod machine_runner; +pub mod provider; +pub mod storage; +pub mod sync; + +// Shared primitives, folded in from the old common-rs crates: the +// node is their only consumer, and if it ever extracts to its own +// repo they travel inside it. +pub mod arithmetic; +pub mod kms; +pub mod merkle; + +// The dispute engine: the spec-oracled geometry core (engine) and +// its consumers, the hero's react loop and the tournament layer. +pub mod engine; +pub mod hero; +pub mod tournament; + +use args::NodeConfig; + +use anyhow::{Result, anyhow}; +use log::{error, info}; +use std::sync::Arc; +use tokio::task::{JoinError, JoinHandle}; + +use crate::blockchain_reader::BlockchainReader; +use crate::chain::Chain; +use crate::epoch_manager::EpochManager; +use crate::machine_runner::MachineRunner; +use crate::sync::ShutdownSignal; +use crate::tournament::EthArenaSender; + +/// Runs the node: one runtime, three workers, explicit spawns and +/// select arms - deliberately not a Worker abstraction, so each edit +/// stays obvious and local. Shutdown is a signal everyone observes +/// (see sync.rs); errors return through JoinHandles. The first exit, +/// or an interrupt, requests shutdown for the rest, and then EVERY +/// remaining handle is awaited - dropping one would detach its task +/// mid-drain, exactly the abrupt-write case crash recovery exists to +/// mop up. A worker that returns before shutdown was requested has +/// stopped unexpectedly: silence is a failure, not a success. +pub async fn run(config: NodeConfig, shutdown: ShutdownSignal) -> Result<()> { + // Startup hygiene, before any worker spawns: sweep the scratch + // a crash or an older node version left behind - settled epochs' + // dispute work and the snapshot store's staging leftovers. Grows + // into the sequencer-style ritual as more checks earn a place. + { + let mut storage = config.storage()?; + storage.sweep_settled_epoch_scratch()?; + storage.sweep_stale_staging()?; + } + + // The machine runner is the blocking lane (machine execution + + // SQLite): plain sync code on a blocking thread. The chain-facing + // workers are async tasks. (The Hero's dispute loop still runs + // inside the epoch manager's task and pins a runtime worker + // during machine work; moving it to the blocking lane is the + // sync-core phase of docs/plans/simplification.md.) + let mut machine_runner: JoinHandle> = { + let params = config.clone(); + let shutdown = shutdown.clone(); + tokio::task::spawn_blocking(move || { + let storage = params.storage()?; + let mut machine_runner = MachineRunner::new(storage, params.sleep_duration)?; + machine_runner.start(shutdown)?; + Ok(()) + }) + }; + + let mut blockchain_reader: JoinHandle> = { + let params = config.clone(); + let shutdown = shutdown.clone(); + tokio::spawn(async move { + let storage = params.storage()?; + let chain = Chain::new( + params.provider().await, + params.long_block_range_error_codes.clone(), + ); + let blockchain_reader = + BlockchainReader::new(storage, params.address_book, params.sleep_duration); + blockchain_reader.execution_loop(shutdown, chain).await + }) + }; + + let mut epoch_manager: JoinHandle> = { + let params = config.clone(); + let shutdown = shutdown.clone(); + tokio::spawn(async move { + // the epoch manager's own handle only reads; the Hero it + // spawns opens its own writer + let storage = params.storage_read_only()?; + let chain = Chain::new( + params.provider().await, + params.long_block_range_error_codes.clone(), + ); + let arena_sender = + EthArenaSender::new(chain.provider().clone()).expect("could not create sender"); + let epoch_manager = EpochManager::new( + Arc::new(arena_sender), + params.address_book.consensus, + params.signer_address, + storage, + params.sleep_duration, + ); + epoch_manager.execution_loop(shutdown, chain).await?; + Ok(()) + }) + }; + + // Race the interrupt against every worker; biased so a pending + // interrupt beats a ready worker exit. + let mut finished = (false, false, false); + let first_exit: Option<(&str, std::result::Result, JoinError>)> = tokio::select! { + biased; + _ = tokio::signal::ctrl_c() => { + info!("interrupt received, starting shutdown"); + None + } + r = &mut machine_runner => { finished.0 = true; Some(("machine runner", r)) } + r = &mut blockchain_reader => { finished.1 = true; Some(("blockchain reader", r)) } + r = &mut epoch_manager => { finished.2 = true; Some(("epoch manager", r)) } + }; + + // Evaluated before the request below: a worker exit under an + // externally requested shutdown is graceful, one before it is not. + let failure = + first_exit.and_then(|(name, joined)| worker_failure(name, joined, shutdown.is_requested())); + + shutdown.request(); + + if !finished.0 { + report_drained("machine runner", machine_runner.await); + } + if !finished.1 { + report_drained("blockchain reader", blockchain_reader.await); + } + if !finished.2 { + report_drained("epoch manager", epoch_manager.await); + } + + match failure { + Some(e) => Err(e), + None => Ok(()), + } +} + +fn worker_failure( + name: &str, + joined: std::result::Result, JoinError>, + shutdown_requested: bool, +) -> Option { + match joined { + Ok(Ok(())) if shutdown_requested => { + info!("{name} shutdown gracefully"); + None + } + Ok(Ok(())) => Some(anyhow!("{name} stopped unexpectedly")), + Ok(Err(e)) => { + error!("{name} returned error: {e:#}"); + Some(e) + } + Err(join_error) => Some(anyhow!("{name} panicked: {join_error}")), + } +} + +fn report_drained(name: &str, joined: std::result::Result, JoinError>) { + match joined { + Ok(Ok(())) => info!("{name} shutdown gracefully"), + Ok(Err(e)) => error!("{name} exited with error during shutdown: {e:#}"), + Err(join_error) => error!("{name} panicked during shutdown: {join_error}"), + } +} diff --git a/cartesi-rollups/node/machine-runner/src/error.rs b/cartesi-rollups/node/src/machine_runner/error.rs similarity index 70% rename from cartesi-rollups/node/machine-runner/src/error.rs rename to cartesi-rollups/node/src/machine_runner/error.rs index 790cf134b..92fc34938 100644 --- a/cartesi-rollups/node/machine-runner/src/error.rs +++ b/cartesi-rollups/node/src/machine_runner/error.rs @@ -1,9 +1,9 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -use cartesi_dave_merkle::DigestError; +use crate::merkle::DigestError; +use crate::storage::StorageError; use cartesi_machine::error::MachineError; -use rollups_state_manager::StateAccessError; use thiserror::Error; @@ -30,10 +30,18 @@ pub enum MachineRunnerError { #[error("Couldn't complete machine run with: `{reason}`")] MachineRunFail { reason: u32 }, + // The engine's verbs (collect, the stf) speak anyhow; geometry + // violations stay panics per the stf module doc. + #[error(transparent)] + Engine { + #[from] + source: anyhow::Error, + }, + #[error(transparent)] StateManagerError { #[from] - source: StateAccessError, + source: StorageError, }, } diff --git a/cartesi-rollups/node/src/machine_runner/mod.rs b/cartesi-rollups/node/src/machine_runner/mod.rs new file mode 100644 index 000000000..8b87e427f --- /dev/null +++ b/cartesi-rollups/node/src/machine_runner/mod.rs @@ -0,0 +1,141 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pub mod error; + +use self::error::Result; +use std::time::Duration; + +use crate::engine::{MachineStf, Ruler, Stf, Structure}; +use crate::storage::rollups_machine::LOG2_STRIDE; +use crate::storage::{InputId, Storage}; +use crate::sync::ShutdownSignal; + +pub struct MachineRunner { + storage: Storage, + sleep_duration: Duration, + structure: Structure, +} + +impl MachineRunner { + pub fn new(storage: Storage, sleep_duration: Duration) -> Result { + let structure = storage.sling_config()?.structure; + Ok(Self { + storage, + sleep_duration, + structure, + }) + } + + pub fn start(&mut self, shutdown: ShutdownSignal) -> Result<()> { + loop { + // A failed pass is retried, not fatal: the advance batch + // is one transaction and replay absorbs re-execution, so + // a transient failure costs one polling interval, never + // the validator. Invariant violations are asserts and + // stay fatal through the panic path. + if let Err(e) = self.process_rollup() { + log::warn!("machine advance failed, retrying next tick: {e}"); + } + + // all inputs have been processed up to this point, + // sleep and come back later + if shutdown.wait_timeout(self.sleep_duration) { + break Ok(()); + } + } + } + + fn process_rollup(&mut self) -> Result<()> { + // process all inputs that are currently availalble + loop { + self.catch_up()?; + + let current_machine_epoch = self.storage.next_input_id()?.epoch_number; + let latest_blockchain_epoch = self.storage.epoch_count()?; + + if current_machine_epoch == latest_blockchain_epoch { + // all current inputs processed in current epoch, which is still open. + // sleep and come back later. + break Ok(()); + } else { + // epoch is finished, all inputs processed + assert!(current_machine_epoch < latest_blockchain_epoch); + self.storage.roll_epoch()?; + log::info!("started new epoch {}", current_machine_epoch + 1); + } + } + } + + /// Processes available inputs in batches of the snapshot gap: + /// each pass reloads the machine from the newest boundary, + /// records up to a batch of inputs, and commits their rows in one + /// transaction. Restart and tick are the same code path; a crash + /// re-executes at most one batch. + fn catch_up(&mut self) -> Result<()> { + let batch_size = self.storage.snapshot_gap_inputs(); + + loop { + let (mut machine, mut batch) = self.storage.begin_advances()?; + + while (batch.len() as u64) < batch_size { + let input_id = InputId { + epoch_number: machine.epoch(), + input_index_in_epoch: machine.next_input_index_in_epoch(), + }; + let Some(input) = self.storage.input(&input_id)? else { + break; + }; + + log::info!( + "processing input {}:{}", + input.id.epoch_number, + input.id.input_index_in_epoch + ); + + // One window-sized engine collect on the working + // clone: the same geometry the dispute replays, + // scheduled forward. The machine moves out for the + // window and back for the record verbs, which own + // the clone swap either way. + let window = input_id.input_index_in_epoch; + let mut stf = MachineStf::over_advancing( + machine.take_machine(), + window, + input.data, + batch.boundary_path().to_path_buf(), + ); + assert!( + stf.yielded()? && !stf.halted()?, + "the working clone must be yielded awaiting the input" + ); + let mut ruler = Ruler::new_at( + stf, + self.structure, + window + 1, + self.structure.window_start(window), + ); + let runs = ruler.collect(self.structure.window_start(window + 1), LOG2_STRIDE)?; + let stf = ruler.into_stf(); + let reverted = stf.took_revert(); + machine.put_machine(stf.into_machine()); + machine.increment_input(); + + if reverted { + self.storage + .record_reverted(&mut batch, &mut machine, &runs)?; + } else { + self.storage + .record_accepted(&mut batch, &mut machine, &runs)?; + } + } + + let exhausted = (batch.len() as u64) < batch_size; + self.storage.commit_advances(batch)?; + + if exhausted { + break Ok(()); + } + } + } +} diff --git a/cartesi-rollups/node/src/main.rs b/cartesi-rollups/node/src/main.rs new file mode 100644 index 000000000..973219719 --- /dev/null +++ b/cartesi-rollups/node/src/main.rs @@ -0,0 +1,19 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use cartesi_rollups_prt_node::{args::NodeConfig, run, sync::ShutdownSignal}; + +use anyhow::Result; +use env_logger::Env; +use log::info; + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + info!("Hello from PRT Rollup Node!"); + + let (config, _storage) = NodeConfig::setup().await; + info!("Running with config:\n{}", config); + + run(config, ShutdownSignal::default()).await +} diff --git a/common-rs/merkle/src/digest/keccak.rs b/cartesi-rollups/node/src/merkle/digest/keccak.rs similarity index 100% rename from common-rs/merkle/src/digest/keccak.rs rename to cartesi-rollups/node/src/merkle/digest/keccak.rs diff --git a/common-rs/merkle/src/digest/mod.rs b/cartesi-rollups/node/src/merkle/digest/mod.rs similarity index 100% rename from common-rs/merkle/src/digest/mod.rs rename to cartesi-rollups/node/src/merkle/digest/mod.rs diff --git a/common-rs/merkle/src/lib.rs b/cartesi-rollups/node/src/merkle/mod.rs similarity index 89% rename from common-rs/merkle/src/lib.rs rename to cartesi-rollups/node/src/merkle/mod.rs index f2bcfa06d..346fe5e0d 100644 --- a/common-rs/merkle/src/lib.rs +++ b/cartesi-rollups/node/src/merkle/mod.rs @@ -4,7 +4,7 @@ //! //! # Examples //! ```rust -//! use cartesi_dave_merkle::{Digest, MerkleBuilder}; +//! use cartesi_rollups_prt_node::merkle::{Digest, MerkleBuilder}; //! //! let mut builder = MerkleBuilder::default(); //! builder.append(Digest::ZERO); diff --git a/common-rs/merkle/src/tree.rs b/cartesi-rollups/node/src/merkle/tree.rs similarity index 96% rename from common-rs/merkle/src/tree.rs rename to cartesi-rollups/node/src/merkle/tree.rs index 2bf017a63..d6a185b69 100644 --- a/common-rs/merkle/src/tree.rs +++ b/cartesi-rollups/node/src/merkle/tree.rs @@ -1,7 +1,7 @@ //! This module contains the [MerkleTree] struct and related types like the //! [MerkleProof]. -use crate::Digest; +use crate::merkle::Digest; use ruint::{UintTryFrom, aliases::U256}; use std::{ops::Rem, sync::Arc}; @@ -224,7 +224,7 @@ impl MerkleTree { #[cfg(test)] mod tests { - use crate::{Digest, MerkleTree}; + use crate::merkle::{Digest, MerkleTree}; fn one_digest() -> Digest { Digest::from_digest_hex( @@ -250,7 +250,7 @@ mod tests { #[test] pub fn test_tree() { - let mut builder = crate::MerkleBuilder::default(); + let mut builder = crate::merkle::MerkleBuilder::default(); builder.append_repeated(Digest::ZERO, 2); builder.append_repeated(Digest::ZERO, 2u128.pow(64) - 2); let tree = builder.build(); @@ -261,7 +261,7 @@ mod tests { #[test] pub fn proof_test() { - let mut builder = crate::MerkleBuilder::default(); + let mut builder = crate::merkle::MerkleBuilder::default(); for _ in 0..8 { builder.append(one_digest()); builder.append(Digest::ZERO); @@ -280,7 +280,7 @@ mod tests { #[test] pub fn proof_test_2() { - let mut builder = crate::MerkleBuilder::default(); + let mut builder = crate::merkle::MerkleBuilder::default(); let hashes = { let h = [ "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -312,7 +312,7 @@ mod tests { #[test] pub fn last_proof_test() { - let mut builder = crate::MerkleBuilder::default(); + let mut builder = crate::merkle::MerkleBuilder::default(); builder.append_repeated(Digest::ZERO, 2); builder.append_repeated(Digest::ZERO, 2u128.pow(64) - 2); let tree = builder.build(); diff --git a/common-rs/merkle/src/tree_builder.rs b/cartesi-rollups/node/src/merkle/tree_builder.rs similarity index 94% rename from common-rs/merkle/src/tree_builder.rs rename to cartesi-rollups/node/src/merkle/tree_builder.rs index d5bce082d..0fa86284b 100644 --- a/common-rs/merkle/src/tree_builder.rs +++ b/cartesi-rollups/node/src/merkle/tree_builder.rs @@ -1,6 +1,6 @@ //! Module for building merkle trees from leafs. -use crate::MerkleTree; +use crate::merkle::MerkleTree; use ruint::{UintTryFrom, aliases::U256}; use std::sync::Arc; @@ -150,7 +150,7 @@ fn is_count_pow2(count: U256) -> bool { #[cfg(test)] mod tests { use super::MerkleBuilder; - use crate::{Digest, MerkleTree}; + use crate::merkle::{Digest, MerkleTree}; use ruint::aliases::U256; fn one_digest() -> Digest { @@ -162,12 +162,12 @@ mod tests { #[test] fn test_is_pow2() { - assert!(crate::tree_builder::is_count_pow2(U256::from(0))); - assert!(crate::tree_builder::is_count_pow2(U256::from(1))); - assert!(crate::tree_builder::is_count_pow2(U256::from(2))); - assert!(!crate::tree_builder::is_count_pow2(U256::from(3))); - assert!(crate::tree_builder::is_count_pow2(U256::from(4))); - assert!(!crate::tree_builder::is_count_pow2(U256::from(5))); + assert!(crate::merkle::tree_builder::is_count_pow2(U256::from(0))); + assert!(crate::merkle::tree_builder::is_count_pow2(U256::from(1))); + assert!(crate::merkle::tree_builder::is_count_pow2(U256::from(2))); + assert!(!crate::merkle::tree_builder::is_count_pow2(U256::from(3))); + assert!(crate::merkle::tree_builder::is_count_pow2(U256::from(4))); + assert!(!crate::merkle::tree_builder::is_count_pow2(U256::from(5))); } #[test] diff --git a/cartesi-rollups/node/cartesi-rollups-prt-node/src/provider.rs b/cartesi-rollups/node/src/provider.rs similarity index 96% rename from cartesi-rollups/node/cartesi-rollups-prt-node/src/provider.rs rename to cartesi-rollups/node/src/provider.rs index 42ee248fa..9def9739a 100644 --- a/cartesi-rollups/node/cartesi-rollups-prt-node/src/provider.rs +++ b/cartesi-rollups/node/src/provider.rs @@ -1,6 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +use crate::kms::{CommonSignature, KmsSignerBuilder}; use alloy::{ network::{Ethereum, EthereumWallet, NetworkWallet}, primitives::Address, @@ -11,7 +12,6 @@ use alloy::{ }; use alloy_chains::NamedChain; use alloy_transport::layers::RetryBackoffLayer; -use cartesi_dave_kms::{CommonSignature, KmsSignerBuilder}; use std::{fs, str::FromStr, time::Duration}; use crate::args::SignerArgs; @@ -34,7 +34,7 @@ async fn create_signer( .trim() .to_string() } else { - web3_private_key.clone().unwrap() //.unwrap_or(ANVIL_KEY_1.to_string()) + web3_private_key.clone().unwrap() }; let local_signer = diff --git a/cartesi-rollups/node/src/storage/advance.rs b/cartesi-rollups/node/src/storage/advance.rs new file mode 100644 index 000000000..bcd0eb3e4 --- /dev/null +++ b/cartesi-rollups/node/src/storage/advance.rs @@ -0,0 +1,775 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The machine runner's writer role: window roots, snapshot +//! boundaries, epoch roll, and epoch GC. Machine store mechanics and +//! the snapshot rows live in the boundary store (snapshots.rs); this +//! role folds each window's runs into its root row and batches them +//! with the boundaries. +//! +//! The advance path is filesystem-first, database-second: machines +//! land in the content-addressed store before any row references +//! them, and directories are removed only after the commit that +//! unreferenced them. A crash can orphan a directory, never dangle a +//! row. Rows for a whole batch of inputs commit in ONE transaction +//! (cadence = the snapshot gap), so the per-input crash window that +//! produced the verify-on-conflict fix (cff83f7) is structurally +//! gone; the verify remains as a pure nondeterminism tripwire. + +use super::convert::u64_to_i64; +use super::error::Result; +use super::open::{create_epoch_dir, snapshots_path}; +use super::rollups_machine::RollupsMachine; +use super::snapshots::{ + gc_previous_advances_in, insert_snapshot_in, remove_orphan_dirs, + sweep_scratch_dirs_at_or_below, sweep_unreferenced_snapshots_in, +}; +use super::{Settlement, Storage, rollups_machine}; +use crate::engine::Run; + +use crate::merkle::Digest; +use cartesi_machine::types::Hash; +use rusqlite::{Transaction, params}; +use std::path::{Path, PathBuf}; + +/// One tick's worth of processed inputs, accumulated in memory and +/// committed atomically. Dropping an uncommitted batch abandons only +/// idempotent content-addressed files and staging clones (swept on +/// drop and at startup) - the database never sees it. +#[derive(Debug)] +pub struct AdvanceBatch { + epoch: u64, + /// The boundary the machine currently sits on: the state after + /// every recorded input, keyed (epoch, boundary_input). Also the + /// revert restore point - reverts never read the database. + boundary_input: u64, + boundary_hash: Hash, + boundary_path: PathBuf, + /// The live working clone the machine mutates in place; committed + /// into the CAS at each accepted input and replaced by a fresh + /// clone. Always the machine's backing directory. + working: PathBuf, + records: Vec, +} + +impl Drop for AdvanceBatch { + /// The chain's spare clone (or a mid-batch abandonment) is + /// scratch; best-effort removal here, the startup sweep as the + /// backstop. Unlinking under a still-open machine is safe: the + /// mappings hold the files alive until that machine drops. + fn drop(&mut self) { + if self.working.exists() + && let Err(e) = std::fs::remove_dir_all(&self.working) + { + log::warn!( + "working clone `{}` not removed: {e}", + self.working.display() + ); + } + } +} + +#[derive(Debug)] +struct AdvanceRecord { + input_number: u64, + /// The window's level-0 subtree root, folded from the collect's + /// runs at record time: the runner's one level-0 artifact + /// (one-engine.md section 6, as amended). The unfolded runs are + /// never persisted. + window_root: Digest, + /// State after this input, keyed (epoch, input_number + 1). A + /// reverted input shares its predecessor's snapshot. + boundary_hash: Hash, + boundary_path: PathBuf, +} + +/// One window's runs folded into its root row's value. Tiling is the +/// engine's geometry contract, so a violation is a panic, not an +/// error. +fn fold_window_root(runs: &[Run]) -> Digest { + crate::engine::fold_runs( + runs.iter().map(|run| { + ( + run.hash, + u64::try_from(run.repetitions).expect("window runs fit u64"), + ) + }), + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + ) + .expect("recorded runs tile their window (the collect pads the tail)") + .root_hash() +} + +impl AdvanceBatch { + pub fn len(&self) -> usize { + self.records.len() + } + + pub fn is_empty(&self) -> bool { + self.records.is_empty() + } + + /// The committed directory of the state the working clone was + /// checked out from: the advance stf's revert restore point. + pub fn boundary_path(&self) -> &Path { + &self.boundary_path + } +} + +impl Storage { + /// Opens a batch on a working clone of the newest boundary: the + /// machine mutates the clone in place (the chain of clones, + /// docs/plans/snapshots.md), so committed boundaries are never + /// touched. Restart and tick are the same code path: a crash + /// re-executes at most one batch of inputs from staging swept + /// clean. + pub fn begin_advances(&mut self) -> Result<(RollupsMachine, AdvanceBatch)> { + let (path, epoch, input, hash) = self.read(super::snapshots::latest_boundary_in)?; + let working = self.checkout(&path).map_err(anyhow::Error::from)?; + let machine = RollupsMachine::load_shared(&working, epoch, input)?; + + Ok(( + machine, + AdvanceBatch { + epoch, + boundary_input: input, + boundary_hash: hash, + boundary_path: path, + working, + records: Vec::new(), + }, + )) + } + + /// Records an accepted input: the working clone already holds the + /// post-input state on disk, so recording is close (flush, unlock), + /// commit the clone into the content-addressed store, and continue + /// on a fresh clone of it (filesystem-first; no rows yet). The + /// window's runs fold into their root here; only the root joins + /// the batch. + pub fn record_accepted( + &mut self, + batch: &mut AdvanceBatch, + machine: &mut RollupsMachine, + runs: &[Run], + ) -> Result<()> { + assert!(!runs.is_empty()); + assert_eq!(machine.epoch(), batch.epoch); + let processed = machine.next_input_index_in_epoch() - 1; + assert_eq!( + processed, batch.boundary_input, + "records must be contiguous" + ); + let window_root = fold_window_root(runs); + + // The hash first: it brings the clone's on-disk hash sidecars + // exact, so every later load of this boundary hashes for free. + // The window's final run must carry it (the fixed point the + // collect padded with): the recorded material and the boundary + // row agree by this check at every record. + let hash = machine.state_hash()?; + assert_eq!( + runs.last().expect("nonempty by the assert above").hash, + Digest::new(hash), + "the window's final run must carry the machine's boundary state" + ); + machine.close(); + let working = std::mem::take(&mut batch.working); + let path = self + .commit_clone(working, &hash) + .map_err(anyhow::Error::from)?; + batch.working = self.checkout(&path).map_err(anyhow::Error::from)?; + machine.reopen_shared(&batch.working)?; + + batch.records.push(AdvanceRecord { + input_number: processed, + window_root, + boundary_hash: hash, + boundary_path: path.clone(), + }); + batch.boundary_input = processed + 1; + batch.boundary_hash = hash; + batch.boundary_path = path; + + Ok(()) + } + + /// Records a rejected input: the working clone holds the poisoned + /// post-input state, so it is discarded and the machine continues + /// on a fresh clone of the batch boundary (the canonical pre-input + /// state); the new boundary row will share that snapshot. + pub fn record_reverted( + &mut self, + batch: &mut AdvanceBatch, + machine: &mut RollupsMachine, + runs: &[Run], + ) -> Result<()> { + assert!(!runs.is_empty()); + assert_eq!(machine.epoch(), batch.epoch); + let next_input = machine.next_input_index_in_epoch(); + let processed = next_input - 1; + assert_eq!( + processed, batch.boundary_input, + "records must be contiguous" + ); + let window_root = fold_window_root(runs); + // A reverted window pads with the restored pre-input state: + // exactly the boundary this record reuses. + assert_eq!( + runs.last().expect("nonempty by the assert above").hash, + Digest::new(batch.boundary_hash), + "a reverted window's final run must carry the restored boundary state" + ); + + machine.close(); + self.discard_clone(&batch.working) + .map_err(anyhow::Error::from)?; + batch.working = self + .checkout(&batch.boundary_path) + .map_err(anyhow::Error::from)?; + machine.reopen_shared(&batch.working)?; + + batch.records.push(AdvanceRecord { + input_number: processed, + window_root, + boundary_hash: batch.boundary_hash, + boundary_path: batch.boundary_path.clone(), + }); + batch.boundary_input = next_input; + + Ok(()) + } + + /// Commits the batch: every window-root quartet row, every + /// boundary row, and the gap GC, in one transaction. Directories + /// orphaned by the GC are removed after the commit. + /// + /// The window roots flip increment E's "the open regime never + /// writes sling_nodes" under that note's own frontier rule: a + /// recorded window lies entirely left of the input frontier, so + /// its level-0 subtree root is final - one ordinary cache row per + /// input, absorbed identically on crash replay like every other + /// row here. + pub fn commit_advances(&mut self, batch: AdvanceBatch) -> Result<()> { + if batch.records.is_empty() { + return Ok(()); + } + + let window_roots: Vec<_> = batch + .records + .iter() + .map(|record| { + ( + rollups_machine::window_root_quartet(batch.epoch, record.input_number), + record.window_root, + ) + }) + .collect(); + + let gap = self.snapshot_gap_inputs; + let orphans = self.write(|tx| { + for record in &batch.records { + insert_snapshot_in( + tx, + batch.epoch, + record.input_number + 1, + &record.boundary_hash, + &record.boundary_path, + )?; + } + super::dispute::insert_quartet_nodes_in(tx, &window_roots)?; + gc_previous_advances_in(tx, batch.epoch, batch.boundary_input, gap) + })?; + remove_orphan_dirs(&orphans); + + Ok(()) + } + + /// Closes the epoch: derives the settlement from the recorded + /// hash runs and the machine's outputs, advances the machine into + /// the new epoch, stores its boundary (filesystem-first), then + /// commits settlement + boundary + old-epoch GC in one + /// transaction. + pub fn roll_epoch(&mut self) -> Result<()> { + let mut machine = self.latest_snapshot()?; + let previous_epoch_number = machine.epoch(); + + let computation_hash = self.settlement_root(&mut machine)?; + let (output_merkle, output_proof) = machine.outputs_proof()?; + + machine.finish_epoch(); + + let new_epoch_number = machine.epoch(); + create_epoch_dir(&self.state_dir, new_epoch_number)?; + + let (dest_dir, state_hash) = self + .store_boundary(&mut machine) + .map_err(anyhow::Error::from)?; + + // The post-epoch state the settlement protocol claims and + // stages is exactly the new epoch's initial boundary. + let settlement = Settlement { + computation_hash, + final_state: state_hash, + output_merkle, + output_proof, + }; + + let orphans = self.write(|tx| { + insert_snapshot_in(tx, new_epoch_number, 0, &state_hash, &dest_dir)?; + insert_settlement_in(tx, &settlement, previous_epoch_number)?; + if previous_epoch_number >= 1 { + gc_old_epochs_in(tx, previous_epoch_number - 1) + } else { + Ok(Vec::new()) + } + })?; + remove_orphan_dirs(&orphans); + if previous_epoch_number >= 1 { + sweep_scratch_dirs_at_or_below(&self.state_dir, previous_epoch_number - 1); + } + + self.log_disk_breakdown(new_epoch_number); + + Ok(()) + } + + /// The settlement's computation hash: node(level-0 root) over the + /// persisted window roots plus padding math - the same material + /// and the same fold composition the dispute facade serves, so + /// the root the node settles on IS the root the hero can defend + /// from rows. Strict: every recorded window's row was prepaid by + /// the advance commit; a hole refuses to settle. + /// + /// The padding value is the machine's state at the epoch's final + /// boundary, whose row must agree - a cheap corruption tripwire + /// at every roll (record time already asserted the final run + /// against it). + fn settlement_root(&mut self, machine: &mut RollupsMachine) -> Result { + let epoch = machine.epoch(); + let recorded = machine.next_input_index_in_epoch(); + let boundary = Digest::new(machine.state_hash()?); + + if recorded > 0 { + // Invariant violations panic: the runner's tick loop + // retries Err forever, which would silently stall every + // roll on a corrupt store. + let row = self.snapshot_hash(epoch, recorded)?.unwrap_or_else(|| { + panic!( + "final boundary row missing for epoch {epoch} at input {recorded}: \ + corruption or version drift" + ) + }); + assert_eq!( + Digest::from_digest(&row).map_err(anyhow::Error::from)?, + boundary, + "the final boundary row disagrees with the machine: \ + the settlement root would diverge from the servable root" + ); + } + + let mut roots: Vec<(Digest, u64)> = self + .window_root_range( + epoch, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + recorded, + )? + .into_iter() + .map(|root| (root, 1)) + .collect(); + let max_windows = 1u64 << crate::engine::constants::LOG2_INPUT_SPAN_TO_EPOCH; + if recorded < max_windows { + let pad_root = crate::engine::fold_runs( + [(boundary, rollups_machine::STRIDE_COUNT_IN_INPUT)], + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + )? + .root_hash(); + roots.push((pad_root, max_windows - recorded)); + } + Ok( + crate::engine::fold_runs(roots, crate::engine::constants::LOG2_INPUT_SPAN_TO_EPOCH)? + .root_hash(), + ) + } + + /// The disk baseline, logged at every roll (workstream 1 of + /// docs/plans/node-refactor.md): the snapshot store dominates and + /// its growth rate is what the COW analysis prices. + fn log_disk_breakdown(&self, new_epoch_number: u64) { + let snapshots = dir_size(&snapshots_path(&self.state_dir)); + let db_path = super::open::db_path(&self.state_dir); + let db = file_size(&db_path); + let wal = file_size(&db_path.with_extension("sqlite3-wal")); + let scratch: u64 = (0..=new_epoch_number) + .map(|epoch| dir_size(&self.state_dir.join(epoch.to_string()))) + .sum(); + const MB: f64 = 1024.0 * 1024.0; + log::info!( + "disk after roll to epoch {new_epoch_number}: snapshots {:.1} MB, db {:.1} MB (wal {:.1} MB), epoch scratch {:.1} MB", + snapshots as f64 / MB, + db as f64 / MB, + wal as f64 / MB, + scratch as f64 / MB, + ); + } +} + +/// Write-once cell semantics: an identical replay absorbs, a +/// disagreeing one is nondeterminism and fails loudly. +pub(super) fn insert_settlement_in( + tx: &Transaction, + settlement: &Settlement, + epoch_number: u64, +) -> Result<()> { + let mut stmt = tx + .prepare_cached( + r#" + INSERT INTO settlement_info + (epoch_number, computation_hash, output_merkle, output_proof, final_state) + VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT (epoch_number) DO NOTHING + "#, + ) + .map_err(anyhow::Error::from)?; + + let count = stmt + .execute(params![ + u64_to_i64(epoch_number), + settlement.computation_hash.data(), + &settlement.output_merkle, + &settlement.output_proof.flatten(), + &settlement.final_state, + ]) + .map_err(anyhow::Error::from)?; + + if count == 0 { + let stored = super::queries::settlement_info_in(tx, epoch_number)? + .expect("conflicting settlement row exists by the conflict clause"); + // Invariant violation: panic, never a retryable Err (the + // runner's tick loop would replay the disagreement forever). + assert_eq!( + stored, *settlement, + "settlement for epoch {epoch_number} disagrees with its stored row: \ + nondeterminism or corruption" + ); + } + Ok(()) +} + +/// Prunes everything at or below `max_epoch`: boundary rows and the +/// settled epochs' dispute caches. Safe on sling_nodes because +/// DaveConsensus settles epoch N before sealing N + 1, so rows at or +/// below max_epoch belong to finished tournaments. Returns orphaned +/// directories for post-commit removal. +pub(super) fn gc_old_epochs_in(tx: &Transaction, max_epoch: u64) -> Result> { + tx.execute( + "DELETE FROM epoch_snapshot_info WHERE epoch_number <= ?1", + params![u64_to_i64(max_epoch)], + ) + .map_err(anyhow::Error::from)?; + + tx.execute( + "DELETE FROM sling_nodes WHERE epoch <= ?1", + params![u64_to_i64(max_epoch)], + ) + .map_err(anyhow::Error::from)?; + + // The settled disputes' event logs (fold phase 2): keyed by root + // tournament, joined through the epochs table's hex encoding. + tx.execute( + "DELETE FROM tournament_events WHERE root_tournament IN ( + SELECT root_tournament FROM epochs WHERE epoch_number <= ?1 + )", + params![u64_to_i64(max_epoch)], + ) + .map_err(anyhow::Error::from)?; + tx.execute( + "DELETE FROM tournament_events_watermark WHERE root_tournament IN ( + SELECT root_tournament FROM epochs WHERE epoch_number <= ?1 + )", + params![u64_to_i64(max_epoch)], + ) + .map_err(anyhow::Error::from)?; + + sweep_unreferenced_snapshots_in(tx) +} + +/// Best effort: sizes are telemetry, never load-bearing. +fn file_size(path: &Path) -> u64 { + std::fs::metadata(path).map(|m| m.len()).unwrap_or(0) +} + +fn dir_size(path: &Path) -> u64 { + let Ok(entries) = std::fs::read_dir(path) else { + return 0; + }; + entries + .flatten() + .map(|entry| match entry.metadata() { + Ok(meta) if meta.is_dir() => dir_size(&entry.path()), + Ok(meta) => meta.len(), + Err(_) => 0, + }) + .sum() +} + +#[cfg(test)] +mod tests { + use super::super::sql::test_helper::setup_storage; + use super::*; + use crate::merkle::MerkleBuilder; + use crate::storage::Proof; + + /// A window's runs for tests: arbitrary interior, tail carrying + /// the machine's boundary state (the record contract), tiling the + /// window exactly. + fn window_runs(interior: [u8; 32], boundary: Digest) -> Vec { + vec![ + Run { + hash: Digest::new(interior), + repetitions: alloy::primitives::U256::from(5), + }, + Run { + hash: boundary, + repetitions: alloy::primitives::U256::from( + rollups_machine::STRIDE_COUNT_IN_INPUT - 5, + ), + }, + ] + } + + fn boundary_hash(s: &mut Storage) -> Digest { + let mut machine = s.latest_snapshot().unwrap(); + Digest::new(machine.state_hash().unwrap()) + } + + fn sparse_window_roots(s: &mut Storage) { + let rows = [0u64, 2].map(|w| { + ( + rollups_machine::window_root_quartet(9, w), + Digest::new([7; 32]), + ) + }); + s.insert_quartet_nodes(&rows).unwrap(); + } + + // Corruption panics rather than erroring: the tick loops retry + // errors forever, so only a panic reaches the node's loud exit. + #[test] + #[should_panic(expected = "corruption or version drift")] + fn window_root_range_refuses_count_mismatch() { + let (_handle, mut s) = setup_storage(); + sparse_window_roots(&mut s); + let _ = s.window_root_range( + 9, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + 3, + ); + } + + #[test] + #[should_panic(expected = "corruption or version drift")] + fn window_root_range_refuses_holes() { + let (_handle, mut s) = setup_storage(); + sparse_window_roots(&mut s); + // The right count with a hole in the prefix is loud too. + let _ = s.window_root_range( + 9, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + 2, + ); + } + + #[test] + fn settlement_absorbs_identical_refuses_drift() { + let (_handle, mut s) = setup_storage(); + assert!(s.settlement_info(42).unwrap().is_none()); + + let settlement = Settlement { + computation_hash: [0xAA; 32].into(), + final_state: [0xDD; 32], + output_merkle: [0xBB; 32], + output_proof: Proof::new(vec![[0; 32]]), + }; + s.write(|tx| insert_settlement_in(tx, &settlement, 42)) + .unwrap(); + assert_eq!(s.settlement_info(42).unwrap().unwrap(), settlement); + + // an identical replay absorbs + s.write(|tx| insert_settlement_in(tx, &settlement, 42)) + .unwrap(); + } + + #[test] + #[should_panic(expected = "nondeterminism or corruption")] + fn settlement_drift_panics() { + let (_handle, mut s) = setup_storage(); + let settlement = Settlement { + computation_hash: [0xAA; 32].into(), + final_state: [0xDD; 32], + output_merkle: [0xBB; 32], + output_proof: Proof::new(vec![[0; 32]]), + }; + s.write(|tx| insert_settlement_in(tx, &settlement, 42)) + .unwrap(); + + let mut drifted = settlement.clone(); + drifted.output_merkle = [0xCC; 32]; + let _ = s.write(|tx| insert_settlement_in(tx, &drifted, 42)); + } + + /// Every committed record lands its window-root quartet row: + /// (epoch, level-0 stride, window height, shift = window), equal + /// to an independent fold of the record's runs, atomic with the + /// batch. A replayed batch absorbs identically (determinism). + #[test] + fn commit_advances_writes_final_window_roots() { + let (_handle, mut s) = setup_storage(); + let runs = window_runs([7; 32], boundary_hash(&mut s)); + + let (mut machine, mut batch) = s.begin_advances().unwrap(); + machine.increment_input(); + s.record_accepted(&mut batch, &mut machine, &runs).unwrap(); + s.commit_advances(batch).unwrap(); + + let expected = crate::engine::fold_runs( + runs.iter().map(|run| { + ( + run.hash, + u64::try_from(run.repetitions).expect("window-sized"), + ) + }), + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + ) + .unwrap() + .root_hash(); + let quartet = rollups_machine::window_root_quartet(0, 0); + assert_eq!(s.quartet_node(&quartet).unwrap(), Some(expected)); + assert_eq!( + s.window_root_count( + 0, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + 1 + ) + .unwrap(), + 1 + ); + } + + /// An empty epoch settles on its initial state: the settlement + /// root is the padding fold alone, equal to the naive whole-epoch + /// fold of the boundary hash - pinning the tier composition + /// against an independent flat fold. + #[test] + fn roll_of_an_empty_epoch_settles_on_the_initial_state() { + let (_handle, mut s) = setup_storage(); + let hash = { + let mut machine = s.latest_snapshot().unwrap(); + Digest::new(machine.state_hash().unwrap()) + }; + s.roll_epoch().unwrap(); + + let expected = { + let mut builder = MerkleBuilder::default(); + builder.append_repeated(hash, rollups_machine::STRIDE_COUNT_IN_EPOCH); + builder.build().root_hash() + }; + assert_eq!( + s.settlement_info(0).unwrap().unwrap().computation_hash, + expected + ); + } + + /// Runs that do not tile their window are a geometry violation + /// (the collect pads its tail run), so the record panics rather + /// than inventing a window root. + #[test] + #[should_panic(expected = "tile their window")] + fn record_refuses_non_tiling_runs() { + let (_handle, mut s) = setup_storage(); + let boundary = boundary_hash(&mut s); + let short = vec![Run { + hash: boundary, + repetitions: alloy::primitives::U256::from(1), + }]; + let (mut machine, mut batch) = s.begin_advances().unwrap(); + machine.increment_input(); + let _ = s.record_accepted(&mut batch, &mut machine, &short); + } + + /// A window whose final run does not carry the machine's boundary + /// state would make the settlement diverge from the servable + /// root; the record refuses it on the spot. + #[test] + #[should_panic(expected = "boundary state")] + fn record_refuses_runs_disagreeing_with_the_boundary() { + let (_handle, mut s) = setup_storage(); + let runs = window_runs([7; 32], Digest::new([8; 32])); + let (mut machine, mut batch) = s.begin_advances().unwrap(); + machine.increment_input(); + let _ = s.record_accepted(&mut batch, &mut machine, &runs); + } + + /// The batch commit is one transaction: a failure injected at its + /// last step (the GC) must leave zero torn state, and a plain + /// retry after healing must succeed - restart IS the loop. + #[test] + fn commit_advances_is_atomic_under_injected_failure() { + let (_handle, mut s) = setup_storage(); + let full_window = vec![Run { + hash: boundary_hash(&mut s), + repetitions: alloy::primitives::U256::from(rollups_machine::STRIDE_COUNT_IN_INPUT), + }]; + + let raw = rusqlite::Connection::open(crate::storage::open::db_path(s.state_dir())).unwrap(); + raw.execute_batch( + "CREATE TRIGGER fail_snapshot_gc BEFORE DELETE ON epoch_snapshot_info + BEGIN SELECT RAISE(ABORT, 'injected gc failure'); END;", + ) + .unwrap(); + + // Two records: the mid-batch boundary falls to the gap GC, + // whose delete now aborts the whole commit. + let (mut machine, mut batch) = s.begin_advances().unwrap(); + machine.increment_input(); + s.record_accepted(&mut batch, &mut machine, &full_window) + .unwrap(); + machine.increment_input(); + s.record_accepted(&mut batch, &mut machine, &full_window) + .unwrap(); + assert!(s.commit_advances(batch).is_err()); + + assert_eq!( + s.next_input_id().unwrap().input_index_in_epoch, + 0, + "the failed commit must not move the resume point" + ); + assert_eq!( + s.window_root_count( + 0, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + 2 + ) + .unwrap(), + 0, + "the failed commit must not leave window-root rows" + ); + + raw.execute_batch("DROP TRIGGER fail_snapshot_gc").unwrap(); + + let (mut machine, mut batch) = s.begin_advances().unwrap(); + machine.increment_input(); + s.record_accepted(&mut batch, &mut machine, &full_window) + .unwrap(); + machine.increment_input(); + s.record_accepted(&mut batch, &mut machine, &full_window) + .unwrap(); + s.commit_advances(batch).unwrap(); + assert_eq!(s.next_input_id().unwrap().input_index_in_epoch, 2); + } +} diff --git a/cartesi-rollups/node/src/storage/convert.rs b/cartesi-rollups/node/src/storage/convert.rs new file mode 100644 index 000000000..0d4604316 --- /dev/null +++ b/cartesi-rollups/node/src/storage/convert.rs @@ -0,0 +1,33 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Conversions at the SQLite boundary. Integers saturate and blobs +//! produce structured errors: the domain values we persist are always +//! non-negative, well within i64, and exactly 32 bytes where hashes +//! are concerned, so a violation means a corrupted or foreign row - +//! which should degrade or error, never crash the process. + +use crate::merkle::Digest; +use anyhow::anyhow; +use cartesi_machine::types::Hash; + +use super::error::Result; + +pub(super) fn u64_to_i64(value: u64) -> i64 { + i64::try_from(value).unwrap_or(i64::MAX) +} + +pub(super) fn i64_to_u64(value: i64) -> u64 { + value.max(0) as u64 +} + +pub(super) fn blob_to_hash(blob: Vec) -> Result { + let len = blob.len(); + blob.try_into() + .map_err(|_| anyhow!("stored hash has {len} bytes, expected 32").into()) +} + +pub(super) fn blob_to_digest(blob: Vec) -> Result { + Digest::from_digest(&blob) + .map_err(|_| anyhow!("stored digest has {} bytes, expected 32", blob.len()).into()) +} diff --git a/cartesi-rollups/node/src/storage/dispute.rs b/cartesi-rollups/node/src/storage/dispute.rs new file mode 100644 index 000000000..fb47b4f38 --- /dev/null +++ b/cartesi-rollups/node/src/storage/dispute.rs @@ -0,0 +1,307 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The dispute hero's role: the engine quartet cache, the finalized +//! tournament event log (fold phase 2), and the closed-epoch views +//! it fights tournaments with. +//! +//! The quartet cache's primary key is the coordinate and the hash is +//! the value, so a row, once written, is final - which is what makes +//! a disagreement on insert the nondeterminism tripwire. + +use super::Storage; +use super::convert::{blob_to_digest, i64_to_u64, u64_to_i64}; +use super::error::{Result, StorageError}; +use crate::engine::Quartet; + +use crate::merkle::Digest; +use alloy::{ + hex::ToHexExt, + primitives::{Address, U256}, + rpc::types::Log, +}; +use rusqlite::{OptionalExtension, params}; + +impl Storage { + /// The pinned engine configuration; the migration writes it once. + pub fn sling_config(&self) -> Result { + crate::engine::config::stored(&self.connection) + .map_err(StorageError::InnerError)? + .ok_or_else(|| StorageError::DataNotFound { + description: "engine config row (the migration pins it)".into(), + }) + } + + pub fn quartet_node(&self, quartet: &Quartet) -> Result> { + let hash = self + .connection + .query_row( + "SELECT hash FROM sling_nodes + WHERE epoch = ?1 AND log2_stride = ?2 AND height = ?3 AND shift = ?4", + params![ + u64_to_i64(quartet.epoch), + u64_to_i64(quartet.log2_stride), + u64_to_i64(quartet.height), + shift_blob(&quartet.shift), + ], + |row| row.get::<_, Vec>(0), + ) + .optional() + .map_err(anyhow::Error::from)?; + hash.map(blob_to_digest).transpose() + } + + /// Inserts computed quartet nodes. An existing row must agree; + /// determinism makes concurrent duplicates benign, so a + /// disagreement is the loudest possible signal (nondeterminism or + /// version drift). The schema trigger enforces the same tripwire + /// below this check. + pub fn insert_quartet_nodes(&mut self, rows: &[(Quartet, Digest)]) -> Result<()> { + self.write(|tx| insert_quartet_nodes_in(tx, rows)) + } + + /// How many window-root rows sit in the recorded prefix (shift + /// below `below`). Bounded deliberately: the coordinate also + /// carries machine-bought rows beyond the prefix - a dispute + /// descent through a padding window's root stores its fanout + /// there, a final and correct value - so only the prefix speaks + /// for the open regime. Zero on a store the runner has not + /// processed (or an inputless epoch); the facade cross-checks + /// nonzero counts against the epoch's input count. + pub fn window_root_count( + &mut self, + epoch: u64, + log2_stride: u64, + height: u64, + below: u64, + ) -> Result { + let count: i64 = self + .connection + .query_row( + "SELECT COUNT(*) FROM sling_nodes + WHERE epoch = ?1 AND log2_stride = ?2 AND height = ?3 AND shift < ?4", + params![ + u64_to_i64(epoch), + u64_to_i64(log2_stride), + u64_to_i64(height), + shift_blob(&U256::from(below)), + ], + |row| row.get(0), + ) + .map_err(anyhow::Error::from)?; + Ok(i64_to_u64(count)) + } + + /// The recorded prefix of window roots, in window order, as one + /// range scan bounded to shifts below `expected` (rows beyond the + /// prefix are machine-bought padding roots, not the runner's). + /// Strict within the prefix: the advance commit prepays every + /// recorded window's row, so a hole or a count mismatch is + /// corruption or version drift, never something to heal around. + pub fn window_root_range( + &mut self, + epoch: u64, + log2_stride: u64, + height: u64, + expected: u64, + ) -> Result> { + let rows: Vec<(Vec, Vec)> = self.read(|tx| { + let mut stmt = tx + .prepare_cached( + "SELECT shift, hash FROM sling_nodes + WHERE epoch = ?1 AND log2_stride = ?2 AND height = ?3 AND shift < ?4 + ORDER BY shift ASC", + ) + .map_err(anyhow::Error::from)?; + let rows = stmt + .query_map( + params![ + u64_to_i64(epoch), + u64_to_i64(log2_stride), + u64_to_i64(height), + shift_blob(&U256::from(expected)), + ], + |row| Ok((row.get::<_, Vec>(0)?, row.get::<_, Vec>(1)?)), + ) + .map_err(anyhow::Error::from)?; + rows.collect::>>() + .map_err(|e| anyhow::Error::from(e).into()) + })?; + + // Invariant violations panic (see insert_quartet_nodes_in): + // the tick loops retry Err forever, which would turn a + // corrupt store into a silent livelock while the dispute + // clock runs out. + assert_eq!( + rows.len() as u64, + expected, + "epoch {epoch} has {} window-root rows, expected {expected}: \ + corruption or version drift", + rows.len() + ); + rows.into_iter() + .enumerate() + .map(|(window, (shift, hash))| { + assert_eq!( + shift, + shift_blob(&U256::from(window)), + "window-root rows of epoch {epoch} have a hole at window \ + {window}: corruption or version drift" + ); + blob_to_digest(hash) + }) + .collect() + } + + /// The dispute's persisted event stream, in chain order (block, + /// then log index) - the exact order the tournament fold expects. + /// Only finalized events live here (fold phase 2); the tail past + /// the watermark is refetched live each tick. + pub fn tournament_events(&mut self, root_tournament: Address) -> Result> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + "SELECT raw_log FROM tournament_events + WHERE root_tournament = ?1 + ORDER BY block_number ASC, log_index ASC", + ) + .map_err(anyhow::Error::from)?; + let rows = stmt + .query_map([root_tournament.encode_hex()], |row| { + row.get::<_, Vec>(0) + }) + .map_err(anyhow::Error::from)?; + rows.collect::>>() + .map_err(anyhow::Error::from)? + .into_iter() + .map(|blob| Ok(serde_json::from_slice(&blob).map_err(anyhow::Error::from)?)) + .collect() + }) + } + + /// The highest finalized block whose events are fully persisted + /// for this dispute; None before the first tick persists. + pub fn tournament_events_watermark(&mut self, root_tournament: Address) -> Result> { + let block = self + .connection + .query_row( + "SELECT finalized_block FROM tournament_events_watermark + WHERE root_tournament = ?1", + [root_tournament.encode_hex()], + |row| row.get::<_, i64>(0), + ) + .optional() + .map_err(anyhow::Error::from)?; + Ok(block.map(i64_to_u64)) + } + + /// One tick's finalized harvest: advance the watermark to + /// `finalized_block` and append the events at or below it, in one + /// transaction. The watermark moves first so the schema trigger + /// (events must not outrun it) sees the new bound; it advances + /// even on an empty harvest, keeping the live tail refetch + /// bounded. Replayed ticks are absorbed: identical rows are + /// ignored, and the monotone trigger rejects a rewind. + pub fn append_tournament_events( + &mut self, + root_tournament: Address, + finalized_block: u64, + events: &[&Log], + ) -> Result<()> { + let rows = events + .iter() + .map(|log| { + let block = log + .block_number + .ok_or_else(|| anyhow::anyhow!("chain log without a block number"))?; + let index = log + .log_index + .ok_or_else(|| anyhow::anyhow!("chain log without a log index"))?; + anyhow::ensure!( + block <= finalized_block, + "unfinalized event offered for persistence (block {block} > finalized {finalized_block})" + ); + let blob = serde_json::to_vec(log).map_err(anyhow::Error::from)?; + Ok((block, index, blob)) + }) + .collect::>>()?; + + self.write(|tx| { + tx.execute( + "INSERT INTO tournament_events_watermark VALUES (?1, ?2) + ON CONFLICT (root_tournament) + DO UPDATE SET finalized_block = MAX(finalized_block, excluded.finalized_block)", + params![root_tournament.encode_hex(), u64_to_i64(finalized_block)], + ) + .map_err(anyhow::Error::from)?; + + for (block, index, blob) in &rows { + tx.execute( + "INSERT INTO tournament_events VALUES (?1, ?2, ?3, ?4) + ON CONFLICT DO NOTHING", + params![ + root_tournament.encode_hex(), + u64_to_i64(*block), + u64_to_i64(*index), + blob, + ], + ) + .map_err(anyhow::Error::from)?; + } + Ok(()) + }) + } +} + +/// The transaction body of [`Storage::insert_quartet_nodes`], also +/// batched into the advance commit (the open regime's window-root +/// rows land atomically with their input's hash runs). +pub(super) fn insert_quartet_nodes_in( + tx: &rusqlite::Transaction, + rows: &[(Quartet, Digest)], +) -> Result<()> { + for (quartet, hash) in rows { + let inserted = tx + .execute( + "INSERT INTO sling_nodes VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT DO NOTHING", + params![ + u64_to_i64(quartet.epoch), + u64_to_i64(quartet.log2_stride), + u64_to_i64(quartet.height), + shift_blob(&quartet.shift), + hash.slice(), + ], + ) + .map_err(anyhow::Error::from)?; + if inserted == 0 { + let stored: Vec = tx + .query_row( + "SELECT hash FROM sling_nodes + WHERE epoch = ?1 AND log2_stride = ?2 AND height = ?3 AND shift = ?4", + params![ + u64_to_i64(quartet.epoch), + u64_to_i64(quartet.log2_stride), + u64_to_i64(quartet.height), + shift_blob(&quartet.shift), + ], + |row| row.get(0), + ) + .map_err(anyhow::Error::from)?; + // Invariant violations panic and take the node down (the + // worker join propagates); an Err here would be swallowed + // by the tick loops' warn-and-retry, silencing the + // loudest signal the node has. + assert_eq!( + blob_to_digest(stored)?, + *hash, + "node cache collision at {quartet:?}: nondeterminism or version drift" + ); + } + } + Ok(()) +} + +fn shift_blob(shift: &U256) -> [u8; 32] { + shift.to_be_bytes::<32>() +} diff --git a/cartesi-rollups/node/src/storage/error.rs b/cartesi-rollups/node/src/storage/error.rs new file mode 100644 index 000000000..9ee9a6d5a --- /dev/null +++ b/cartesi-rollups/node/src/storage/error.rs @@ -0,0 +1,36 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use crate::storage::InputId; +use cartesi_machine::error::MachineError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StorageError { + #[error("Supplied Epoch is inconsistent: expected `{expected}`, got `{provided}`")] + InconsistentEpoch { expected: u64, provided: u64 }, + + #[error( + "Supplied Input is inconsistent: previous is `{:?}`, got `{:?}`", + previous, + provided + )] + InconsistentInput { + previous: Option, + provided: InputId, + }, + + #[error("Couldn't find data: `{description}`")] + DataNotFound { description: String }, + + #[error("Machine snapshot error")] + MachineError { + #[from] + source: MachineError, + }, + + #[error("Inner error: `{0}`")] + InnerError(#[from] anyhow::Error), +} + +pub type Result = std::result::Result; diff --git a/cartesi-rollups/node/src/storage/ingest.rs b/cartesi-rollups/node/src/storage/ingest.rs new file mode 100644 index 000000000..31666b069 --- /dev/null +++ b/cartesi-rollups/node/src/storage/ingest.rs @@ -0,0 +1,265 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The blockchain reader's writer role: consensus data. One public +//! operation - the tick's atomic append of watermark, inputs, and +//! epochs. The Rust-side sequencing checks produce the typed errors; +//! the schema triggers back them against raw writers. + +use super::error::{Result, StorageError}; +use super::{Epoch, Input, InputId, Storage}; +use crate::storage::convert::u64_to_i64; + +use alloy::hex::ToHexExt; +use rusqlite::{Transaction, params}; + +impl Storage { + /// Records one blockchain-reader tick: raises the processed-block + /// watermark and appends the tick's inputs and sealed epochs, all + /// in one transaction. Inputs must advance per + /// [`InputId::validate_next`]; epochs must arrive densely. + pub fn insert_consensus_data<'a>( + &mut self, + last_processed_block: u64, + inputs: impl Iterator, + epochs: impl Iterator, + ) -> Result<()> { + self.write(|tx| { + raise_watermark_in(tx, last_processed_block)?; + insert_inputs_in(tx, inputs)?; + insert_epochs_in(tx, epochs)?; + Ok(()) + }) + } +} + +/// Monotonic watermark raise: equal or lower submissions absorb +/// silently, so a replayed tick is a no-op rather than an error. +pub(super) fn raise_watermark_in(tx: &Transaction, block: u64) -> Result<()> { + tx.execute( + r#" + INSERT INTO latest_processed (id, block) VALUES (1, ?1) + ON CONFLICT (id) DO UPDATE SET block = MAX(block, excluded.block) + "#, + params![u64_to_i64(block)], + ) + .map_err(anyhow::Error::from)?; + Ok(()) +} + +fn validate_insert(current: &Option, next: &InputId) -> bool { + match ¤t { + Some(i) if !i.validate_next(next) => false, + None if next.input_index_in_epoch != 0 => false, + _ => true, + } +} + +pub(super) fn insert_inputs_in<'a>( + tx: &Transaction, + inputs: impl Iterator, +) -> Result<()> { + let mut inputs = inputs.peekable(); + if inputs.peek().is_none() { + return Ok(()); + } + + let mut current_input = super::queries::last_input_in(tx)?; + + let mut stmt = tx + .prepare_cached( + r#" + INSERT INTO inputs (epoch_number, input_index_in_epoch, input) + VALUES (?1, ?2, ?3) + "#, + ) + .map_err(anyhow::Error::from)?; + + for input in inputs { + if !validate_insert(¤t_input, &input.id) { + return Err(StorageError::InconsistentInput { + previous: current_input, + provided: input.id.clone(), + }); + } + + stmt.execute(params![ + u64_to_i64(input.id.epoch_number), + u64_to_i64(input.id.input_index_in_epoch), + input.data + ]) + .map_err(anyhow::Error::from)?; + + current_input = Some(input.id.clone()); + } + + Ok(()) +} + +pub(super) fn insert_epochs_in<'a>( + tx: &Transaction, + epochs: impl Iterator, +) -> Result<()> { + let mut epochs = epochs.peekable(); + if epochs.peek().is_none() { + return Ok(()); + } + + let mut stmt = tx + .prepare_cached( + r#" + INSERT INTO epochs + (epoch_number, input_index_boundary, root_tournament, block_created_number) + VALUES (?1, ?2, ?3, ?4) + "#, + ) + .map_err(anyhow::Error::from)?; + + for (next_epoch, epoch) in (super::queries::epoch_count_in(tx)?..).zip(epochs) { + if epoch.epoch_number != next_epoch { + return Err(StorageError::InconsistentEpoch { + expected: next_epoch, + provided: epoch.epoch_number, + }); + } + + stmt.execute(params![ + u64_to_i64(epoch.epoch_number), + u64_to_i64(epoch.input_index_boundary), + epoch.root_tournament.encode_hex(), + u64_to_i64(epoch.block_created_number) + ]) + .map_err(anyhow::Error::from)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::super::sql::test_helper; + use super::*; + use alloy::hex::FromHex; + use alloy::primitives::Address; + + fn storage() -> (tempfile::TempDir, Storage) { + test_helper::setup_storage() + } + + #[test] + fn watermark_rises_and_absorbs_replays() { + let (_handle, mut s) = storage(); + + assert_eq!(s.latest_processed_block().unwrap(), 0); + s.write(|tx| raise_watermark_in(tx, 10)).unwrap(); + assert_eq!(s.latest_processed_block().unwrap(), 10); + + // a replayed or stale tick absorbs instead of erroring + s.write(|tx| raise_watermark_in(tx, 10)).unwrap(); + s.write(|tx| raise_watermark_in(tx, 3)).unwrap(); + assert_eq!(s.latest_processed_block().unwrap(), 10); + + s.write(|tx| raise_watermark_in(tx, 200)).unwrap(); + assert_eq!(s.latest_processed_block().unwrap(), 200); + } + + #[test] + fn inputs_must_be_sequential_and_batches_are_atomic() { + let (_handle, mut s) = storage(); + let data = vec![1u8]; + + let input = |epoch, index| Input { + id: InputId { + epoch_number: epoch, + input_index_in_epoch: index, + }, + data: data.clone(), + }; + + // first input of the database must have index 0 + assert!( + s.insert_consensus_data(1, [&input(0, 1)].into_iter(), [].into_iter()) + .is_err() + ); + + s.insert_consensus_data(2, [&input(0, 0), &input(0, 1)].into_iter(), [].into_iter()) + .unwrap(); + + // a failing batch rolls back whole: the valid prefix does not land + assert!( + s.insert_consensus_data(3, [&input(0, 2), &input(0, 4)].into_iter(), [].into_iter()) + .is_err() + ); + assert_eq!( + s.last_input().unwrap().unwrap().input_index_in_epoch, + 1, + "the failed batch must not leave a partial prefix" + ); + assert_eq!( + s.latest_processed_block().unwrap(), + 2, + "the failed batch must not raise the watermark" + ); + + // an epoch skip re-enters at index 0 + s.insert_consensus_data(4, [&input(0, 2), &input(2, 0)].into_iter(), [].into_iter()) + .unwrap(); + assert!( + s.input(&InputId { + epoch_number: 2, + input_index_in_epoch: 0 + }) + .unwrap() + .is_some() + ); + } + + #[test] + fn epochs_must_be_dense() { + let (_handle, mut s) = storage(); + + let epoch = |number| Epoch { + epoch_number: number, + input_index_boundary: 0, + root_tournament: Address::ZERO, + block_created_number: number * 2, + }; + + assert!(matches!( + s.insert_consensus_data(1, [].into_iter(), [&epoch(1)].into_iter()), + Err(StorageError::InconsistentEpoch { + expected: 0, + provided: 1 + }) + )); + assert_eq!(s.epoch_count().unwrap(), 0); + + s.insert_consensus_data(2, [].into_iter(), [&epoch(0), &epoch(1)].into_iter()) + .unwrap(); + assert_eq!(s.epoch_count().unwrap(), 2); + + // a gapped batch rolls back whole + assert!( + s.insert_consensus_data(3, [].into_iter(), [&epoch(2), &epoch(4)].into_iter()) + .is_err() + ); + assert_eq!(s.epoch_count().unwrap(), 2); + + let tournament = Address::from_hex("0x8dA443F84fEA710266C8eB6bC34B71702d033EF2").unwrap(); + s.insert_consensus_data( + 4, + [].into_iter(), + [&Epoch { + epoch_number: 2, + input_index_boundary: 99, + root_tournament: tournament, + block_created_number: 260, + }] + .into_iter(), + ) + .unwrap(); + let stored = s.last_sealed_epoch().unwrap().unwrap(); + assert_eq!(stored.epoch_number, 2); + assert_eq!(stored.input_index_boundary, 99); + assert_eq!(stored.root_tournament, tournament); + } +} diff --git a/cartesi-rollups/node/src/storage/mod.rs b/cartesi-rollups/node/src/storage/mod.rs new file mode 100644 index 000000000..351b705c7 --- /dev/null +++ b/cartesi-rollups/node/src/storage/mod.rs @@ -0,0 +1,364 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The node's one durable state surface: a single SQLite database +//! plus the content-addressed snapshot store, behind domain +//! operations that preserve the state invariants internally. +//! +//! Layout (docs/plans/node-refactor.md, workstream 3): `open` owns +//! the connection lifecycle and the transaction closure helpers; +//! `ingest` is the blockchain reader's writer role, `advance` the +//! machine runner's, `dispute` the hero's; `queries` is the +//! role-free read surface; `sql` holds the DDL and its discipline +//! tests. Every table belongs to one of four mutation classes - +//! append-only log, write-once cell, monotonic watermark, prunable +//! derived store - enforced by triggers in the schema itself. + +pub mod error; +pub mod rollups_machine; + +pub use error::StorageError; + +mod advance; +mod convert; +mod dispute; +mod ingest; +pub(crate) mod open; +mod queries; +mod snapshots; +pub(crate) mod sql; + +pub use advance::AdvanceBatch; +pub use open::{DEFAULT_SNAPSHOT_GAP_INPUTS, Storage}; + +use self::error::Result; +use crate::merkle::Digest; +use alloy::primitives::Address; +use cartesi_machine::types::Hash; + +pub type Blob = Vec; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Proof(Vec<[u8; 32]>); + +impl Proof { + pub fn new(siblings: Vec<[u8; 32]>) -> Self { + Self(siblings) + } + + pub fn inner(&self) -> Vec<[u8; 32]> { + self.0.clone() + } + + fn from_flattened(input: Vec) -> Result { + if !input.len().is_multiple_of(32) { + return Err(anyhow::anyhow!( + "stored proof has {} bytes, expected a multiple of 32", + input.len() + ) + .into()); + } + + let mut result = Vec::new(); + for chunk in input.chunks(32) { + let mut array = [0u8; 32]; + array.copy_from_slice(chunk); + result.push(array); + } + + Ok(Proof(result)) + } + + fn flatten(&self) -> Vec { + self.0 + .iter() + .flat_map(|array| array.iter()) + .copied() + .collect() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Settlement { + pub computation_hash: Digest, + /// The post-epoch machine state hash: the new epoch's initial + /// boundary, claimed by sentries and staged on-chain. + pub final_state: Hash, + pub output_merkle: Hash, + pub output_proof: Proof, +} + +#[derive(Clone, Debug, Default)] +pub struct InputId { + pub epoch_number: u64, + pub input_index_in_epoch: u64, +} + +impl InputId { + pub fn increment_index(self) -> Self { + Self { + epoch_number: self.epoch_number, + input_index_in_epoch: self.input_index_in_epoch + 1, + } + } + + pub fn increment_epoch(self) -> Self { + Self { + epoch_number: self.epoch_number + 1, + input_index_in_epoch: 0, + } + } + + pub fn validate_next(&self, next: &Self) -> bool { + match self { + InputId { + epoch_number, + input_index_in_epoch, + } if next.epoch_number == *epoch_number + && next.input_index_in_epoch == input_index_in_epoch + 1 => + { + true + } + + InputId { epoch_number, .. } + if next.epoch_number > *epoch_number && next.input_index_in_epoch == 0 => + { + true + } + + _ => false, + } + } +} + +#[derive(Clone, Debug)] +pub struct Input { + pub id: InputId, + pub data: Blob, +} + +#[derive(Clone, Debug)] +pub struct Epoch { + pub epoch_number: u64, + pub input_index_boundary: u64, + pub root_tournament: Address, + pub block_created_number: u64, +} + +#[cfg(test)] +mod tests { + use super::sql::test_helper::setup_storage; + use super::*; + use crate::merkle::MerkleBuilder; + + /// The whole advance lifecycle through the public surface: + /// consensus ingest, a committed advance batch with an accepted + /// and a reverted input, and the epoch roll's settlement. + #[test] + fn test_state_access() -> Result<()> { + let input_0_bytes = b"hello"; + let input_1_bytes = b"world"; + + let (_handle, mut access) = setup_storage(); + + access.insert_consensus_data( + 20, + [ + &Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: 0, + }, + data: input_0_bytes.to_vec(), + }, + &Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: 1, + }, + data: input_1_bytes.to_vec(), + }, + ] + .into_iter(), + [&Epoch { + epoch_number: 0, + input_index_boundary: 12, + root_tournament: Address::ZERO, + block_created_number: 0, + }] + .into_iter(), + )?; + + assert_eq!( + access + .input(&InputId { + epoch_number: 0, + input_index_in_epoch: 0 + })? + .map(|x| x.data), + Some(input_0_bytes.to_vec()), + "input 0 bytes should match" + ); + assert!( + access + .input(&InputId { + epoch_number: 0, + input_index_in_epoch: 2 + })? + .is_none(), + "input 2 shouldn't exist" + ); + + assert!( + access + .insert_consensus_data( + 21, + [&Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: 1, + }, + data: input_0_bytes.to_vec(), + }] + .into_iter(), + [].into_iter(), + ) + .is_err(), + "duplicate input index should fail" + ); + assert!( + access + .insert_consensus_data( + 21, + [&Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: 3, + }, + data: input_0_bytes.to_vec(), + }] + .into_iter(), + [].into_iter(), + ) + .is_err(), + "input index should be sequential" + ); + assert!( + access + .insert_consensus_data( + 21, + [&Input { + id: InputId { + epoch_number: 0, + input_index_in_epoch: 2, + }, + data: input_1_bytes.to_vec(), + }] + .into_iter(), + [].into_iter(), + ) + .is_ok(), + "add sequential input should succeed" + ); + + assert_eq!( + access.latest_processed_block()?, + 21, + "latest block should match" + ); + + // One batch: an accepted input, then a reverted one. The + // reverted input's boundary shares the accepted input's + // snapshot (the machine restores to it). + let (mut machine, mut batch) = access.begin_advances()?; + assert_eq!(machine.epoch(), 0); + + // Records must tile their window and their final run must + // carry the machine's boundary state (the record asserts it); + // the machine never runs in this storage-level test, so every + // boundary is the template hash. + let machine_hash = Digest::new(machine.state_hash()?); + let window_0 = vec![ + crate::engine::Run { + hash: Digest::new([1; 32]), + repetitions: alloy::primitives::U256::from(7), + }, + crate::engine::Run { + hash: machine_hash, + repetitions: alloy::primitives::U256::from( + rollups_machine::STRIDE_COUNT_IN_INPUT - 7, + ), + }, + ]; + let window_1 = vec![crate::engine::Run { + hash: machine_hash, + repetitions: alloy::primitives::U256::from(rollups_machine::STRIDE_COUNT_IN_INPUT), + }]; + + machine.increment_input(); + access.record_accepted(&mut batch, &mut machine, &window_0)?; + + machine.increment_input(); + access.record_reverted(&mut batch, &mut machine, &window_1)?; + assert_eq!( + machine.next_input_index_in_epoch(), + 2, + "the reverted machine resumes after the rejected input" + ); + + assert_eq!(batch.len(), 2); + access.commit_advances(batch)?; + + assert_eq!( + access.window_root_count( + 0, + rollups_machine::LOG2_STRIDE, + rollups_machine::LOG2_STRIDE_COUNT_IN_INPUT, + 2 + )?, + 2, + "both windows landed their root rows" + ); + + assert_eq!( + access.next_input_id()?.input_index_in_epoch, + 2, + "the committed batch is the resume point" + ); + + assert!( + access.settlement_info(1)?.is_none(), + "computation_hash shouldn't exist" + ); + + let (final_state, output_merkle, output_proof) = { + let mut machine = access.latest_snapshot()?; + let (output_merkle, output_proof) = machine.outputs_proof()?; + (machine.state_hash()?, output_merkle, output_proof) + }; + access.roll_epoch()?; + assert_eq!(access.latest_snapshot()?.epoch(), 1); + + // The independent expectation is the naive flat fold of the + // whole epoch (every run, tail-padded to 2^48 leaves) - the + // roll's window-root composition must equal it exactly. + let expected_root = { + let mut builder = MerkleBuilder::default(); + builder.append_repeated(Digest::new([1; 32]), 7u64); + builder.append_repeated(machine_hash, rollups_machine::STRIDE_COUNT_IN_EPOCH - 7); + builder.build().root_hash() + }; + assert_eq!( + access.settlement_info(0)?.unwrap(), + Settlement { + computation_hash: expected_root, + final_state, + output_merkle, + output_proof + }, + "settlement info of epoch 0 should match" + ); + + Ok(()) + } +} diff --git a/cartesi-rollups/node/src/storage/open.rs b/cartesi-rollups/node/src/storage/open.rs new file mode 100644 index 000000000..478090afa --- /dev/null +++ b/cartesi-rollups/node/src/storage/open.rs @@ -0,0 +1,310 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! `Storage` definition, connection lifecycle, and the transaction +//! closure helpers. Writer-role method clusters live in sibling files +//! (`ingest`, `advance`, `dispute`, `queries`), each adding its own +//! `impl Storage`. + +use super::error::Result; +use super::rollups_machine::RollupsMachine; +use super::sql::migrations; +use crate::engine::{EngineConfig, Structure, config as sling_config}; +use crate::merkle::Digest; +use alloy::primitives::Address; +use anyhow::Context; +use cartesi_machine::{format_emulator_version, machine::Machine}; +use rusqlite::{Connection, OpenFlags, Transaction, TransactionBehavior}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +/// SQLite `synchronous` pragma for every connection. NORMAL under WAL +/// survives process crash but may lose the last commits on power +/// loss. That is enough here (docs/plans/node-refactor.md): the node +/// is replay-tolerant by design - every write is re-derivable from +/// the chain and the machine - and it externalizes nothing keyed on a +/// commit. Revisit if a commit ever gates an external effect. +const SYNCHRONOUS_PRAGMA: &str = "NORMAL"; + +/// Snapshot boundaries kept per epoch beyond the start and the +/// latest: every gap-th input. The disk-vs-replay knob for dispute +/// positioning (docs/plans/sling-design.md, increment D); 1 keeps +/// every boundary. Also the advance-batch size: one commit per gap +/// worth of inputs (docs/plans/node-refactor.md, workstream 7). +pub const DEFAULT_SNAPSHOT_GAP_INPUTS: u64 = 64; + +#[derive(Debug)] +pub struct Storage { + pub(super) connection: Connection, + pub(super) state_dir: PathBuf, + pub(super) snapshot_gap_inputs: u64, +} + +impl Storage { + /// Process setup: creates the state directory, runs the + /// migration, seeds the genesis watermark, stores and registers + /// the template machine, and pins the engine configuration (which + /// fails loudly on app or emulator drift against an existing + /// state dir). + pub fn migrate( + state_dir: &Path, + initial_machine_path: &Path, + genesis_block_number: u64, + app_address: Address, + ) -> Result { + create_directory_structure(state_dir)?; + let state_dir = state_dir.canonicalize().map_err(anyhow::Error::from)?; + + let mut connection = open_writer_connection(&db_path(&state_dir))?; + migrations::migrate_to_latest(&mut connection)?; + + let mut storage = Self { + connection, + state_dir, + snapshot_gap_inputs: DEFAULT_SNAPSHOT_GAP_INPUTS, + }; + + storage.set_genesis(genesis_block_number)?; + let template_hash = storage.set_initial_machine(initial_machine_path)?; + + sling_config::pin( + &storage.connection, + &EngineConfig { + structure: Structure::PRODUCTION, + app: app_address.as_slice().to_vec(), + template_hash: Digest::from_digest(&template_hash).map_err(anyhow::Error::from)?, + emulator_version: format_emulator_version(Machine::version()), + }, + )?; + + Ok(storage) + } + + /// A writer handle onto an already-migrated database. One + /// connection per worker thread; SQLite's WAL plus the busy + /// timeout arbitrate between them. + pub fn new(state_dir: &Path) -> Result { + let state_dir = state_dir.canonicalize().map_err(anyhow::Error::from)?; + let connection = open_writer_connection(&db_path(&state_dir))?; + + Ok(Self { + connection, + state_dir, + snapshot_gap_inputs: DEFAULT_SNAPSHOT_GAP_INPUTS, + }) + } + + /// A read-only handle: the connection refuses writes outright and + /// fails fast under write pressure rather than stalling a tick. + pub fn open_read_only(state_dir: &Path) -> Result { + let state_dir = state_dir.canonicalize().map_err(anyhow::Error::from)?; + let connection = open_reader_connection(&db_path(&state_dir))?; + + Ok(Self { + connection, + state_dir, + snapshot_gap_inputs: DEFAULT_SNAPSHOT_GAP_INPUTS, + }) + } + + pub fn set_snapshot_gap_inputs(&mut self, gap: u64) { + assert!(gap >= 1, "the gap divides input numbers"); + self.snapshot_gap_inputs = gap; + } + + pub fn snapshot_gap_inputs(&self) -> u64 { + self.snapshot_gap_inputs + } + + pub fn state_dir(&self) -> &Path { + &self.state_dir + } + + /// Runs `f` inside a Deferred transaction, committing on success. + /// For reads: Deferred takes no write lock, so readers never + /// block writers, and multi-statement reads still see one + /// snapshot. + pub(super) fn read(&mut self, f: impl FnOnce(&Transaction) -> Result) -> Result { + let tx = self + .connection + .transaction_with_behavior(TransactionBehavior::Deferred) + .map_err(anyhow::Error::from)?; + let out = f(&tx)?; + tx.commit().map_err(anyhow::Error::from)?; + Ok(out) + } + + /// Runs `f` inside an Immediate transaction, committing on + /// success. For mutations: Immediate takes the write lock at + /// BEGIN, so a contending writer waits at the boundary of the + /// domain operation instead of failing mid-transaction. On `Err` + /// the transaction drops unsent - automatic rollback. + /// + /// Corruption tripwires escalate to panics here: the schema + /// triggers (defense in depth BELOW the Rust-side checks) surface + /// as ordinary rusqlite errors, and the workers' tick loops retry + /// every error forever - a nondeterminism abort must instead + /// reach the node's loud exit path (lib.rs worker_failure). The + /// fragments are the triggers' own messages, pinned by the + /// discipline tests. + pub(super) fn write(&mut self, f: impl FnOnce(&Transaction) -> Result) -> Result { + let tx = self + .connection + .transaction_with_behavior(TransactionBehavior::Immediate) + .map_err(anyhow::Error::from)?; + let out = escalate_tripwires(f(&tx))?; + tx.commit().map_err(anyhow::Error::from)?; + Ok(out) + } + + fn set_genesis(&mut self, block_number: u64) -> Result<()> { + self.write(|tx| super::ingest::raise_watermark_in(tx, block_number)) + } + + /// Stores the initial machine into the content-addressed store + /// and registers it as both the epoch-0 boundary and the template + /// (filesystem first, then one transaction for the rows). + fn set_initial_machine( + &mut self, + source_machine_path: &Path, + ) -> Result { + assert!( + source_machine_path.is_dir(), + "machine path `{}` must be an existing directory", + source_machine_path.display() + ); + + // Hash through a private load (cheap: the image ships valid + // hash sidecars), then clone the image into the store - no + // 500 MB re-serialization through machine memory. Cross- + // filesystem imports degrade to a sparse copy inside the + // clone. + let state_hash = RollupsMachine::new(source_machine_path, 0, 0)?.state_hash()?; + let working = self + .checkout(source_machine_path) + .map_err(anyhow::Error::from)?; + let dest_machine_path = self + .commit_clone(working, &state_hash) + .map_err(anyhow::Error::from)?; + + self.write(|tx| { + super::snapshots::insert_snapshot_in(tx, 0, 0, &state_hash, &dest_machine_path)?; + super::snapshots::insert_template_machine_in(tx, &state_hash)?; + Ok(()) + })?; + + Ok(state_hash) + } + + /// The per-epoch scratch directory (dispute logs and artifacts); + /// filesystem lifecycle, not SQL. + pub fn epoch_directory(&mut self, epoch_number: u64) -> Result { + create_epoch_dir(&self.state_dir, epoch_number) + } +} + +/// Writer connections: WAL, enforced foreign keys, NORMAL sync, and a +/// generous busy timeout (machine work happens between transactions, +/// never inside one, so writers only contend for row-commit bursts). +/// Escalates the schema triggers' corruption tripwires into panics. +/// The Rust-side checks already panic at their sites; the triggers +/// beneath them (defense in depth, and the only check raw writers +/// meet) abort with these exact message fragments - pinned by the +/// discipline tests - and would otherwise flow into the workers' +/// retry-forever tick loops as ordinary errors. Discipline refusals +/// that are part of an API's contract (append-only, validate_next) +/// stay errors: callers legitimately observe those. +fn escalate_tripwires(result: Result) -> Result { + if let Err(e) = &result { + let text = format!("{e:#}"); + for fragment in [ + "nondeterminism or corruption", + "node cache collision", + "corruption or version drift", + "disagrees with its stored row", + ] { + assert!( + !text.contains(fragment), + "storage tripwire fired: {text} (invariant violation, not retryable)" + ); + } + } + result +} + +fn open_writer_connection(db_path: &Path) -> Result { + let connection = Connection::open(db_path).map_err(anyhow::Error::from)?; + configure_writer_pragmas(&connection)?; + Ok(connection) +} + +fn configure_writer_pragmas(connection: &Connection) -> Result<()> { + // Foreign keys are per-connection in SQLite and default OFF in + // stock builds; without this pragma the schema's ON DELETE + // RESTRICT protections are declarative only. + connection + .pragma_update(None, "foreign_keys", "ON") + .map_err(anyhow::Error::from)?; + connection + .pragma_update(None, "journal_mode", "WAL") + .map_err(anyhow::Error::from)?; + connection + .pragma_update(None, "synchronous", SYNCHRONOUS_PRAGMA) + .map_err(anyhow::Error::from)?; + connection + .busy_timeout(std::time::Duration::from_secs(10)) + .map_err(anyhow::Error::from)?; + Ok(()) +} + +fn open_reader_connection(db_path: &Path) -> Result { + let connection = Connection::open_with_flags(db_path, OpenFlags::SQLITE_OPEN_READ_ONLY) + .map_err(anyhow::Error::from)?; + connection + .pragma_update(None, "query_only", "ON") + .map_err(anyhow::Error::from)?; + connection + .busy_timeout(std::time::Duration::from_millis(100)) + .map_err(anyhow::Error::from)?; + Ok(connection) +} + +// +// State directory layout +// + +pub fn db_path(state_dir: &Path) -> PathBuf { + state_dir.to_owned().join("db.sqlite3") +} + +pub fn snapshots_path(state_dir: &Path) -> PathBuf { + state_dir.to_owned().join("snapshots") +} + +pub fn create_empty_state_dir_if_needed(state_dir: &Path) -> Result<()> { + fs::create_dir_all(state_dir).with_context(|| format!("creating `{}`", state_dir.display()))?; + Ok(()) +} + +fn create_directory_structure(state_dir: &Path) -> Result<()> { + create_empty_state_dir_if_needed(state_dir)?; + + let snapshots_path = snapshots_path(state_dir); + fs::create_dir_all(&snapshots_path) + .with_context(|| format!("creating `{}`", &snapshots_path.display()))?; + + Ok(()) +} + +fn epoch_dir(state_dir: &Path, epoch_number: u64) -> PathBuf { + state_dir.join(epoch_number.to_string()) +} + +pub(super) fn create_epoch_dir(state_dir: &Path, epoch_number: u64) -> Result { + let path = epoch_dir(state_dir, epoch_number); + fs::create_dir_all(&path).with_context(|| format!("creating `{}`", &path.display()))?; + + Ok(path) +} diff --git a/cartesi-rollups/node/src/storage/queries.rs b/cartesi-rollups/node/src/storage/queries.rs new file mode 100644 index 000000000..08e185dc0 --- /dev/null +++ b/cartesi-rollups/node/src/storage/queries.rs @@ -0,0 +1,231 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The role-free read surface: point reads any worker may issue. +//! Writes are role-locked to their files (`ingest`, `advance`, +//! `dispute`); reads are shared vocabulary. + +use super::convert::{blob_to_hash, i64_to_u64, u64_to_i64}; +use super::error::{Result, StorageError}; +use super::{Epoch, Input, InputId, Proof, Settlement, Storage}; + +use alloy::hex::FromHex; +use alloy::primitives::Address; +use rusqlite::{OptionalExtension, Transaction, params}; + +impl Storage { + pub fn latest_processed_block(&mut self) -> Result { + self.read(|tx| { + let block: i64 = tx + .query_row("SELECT block FROM latest_processed WHERE id = 1", [], |r| { + r.get(0) + }) + .map_err(anyhow::Error::from)?; + Ok(i64_to_u64(block)) + }) + } + + pub fn epoch_count(&mut self) -> Result { + self.read(epoch_count_in) + } + + pub fn last_sealed_epoch(&mut self) -> Result> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT epoch_number, input_index_boundary, root_tournament, + block_created_number + FROM epochs + ORDER BY epoch_number DESC + LIMIT 1 + "#, + ) + .map_err(anyhow::Error::from)?; + + stmt.query_row([], row_to_epoch) + .optional() + .map_err(anyhow::Error::from)? + .transpose() + }) + } + + pub fn input(&mut self, id: &InputId) -> Result> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT input FROM inputs + WHERE epoch_number = ?1 AND input_index_in_epoch = ?2 + "#, + ) + .map_err(anyhow::Error::from)?; + + let data = stmt + .query_row( + params![ + u64_to_i64(id.epoch_number), + u64_to_i64(id.input_index_in_epoch) + ], + |row| row.get(0), + ) + .optional() + .map_err(anyhow::Error::from)?; + + Ok(data.map(|data| Input { + id: id.clone(), + data, + })) + }) + } + + pub fn inputs(&mut self, epoch_number: u64) -> Result>> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT input FROM inputs + WHERE epoch_number = ?1 + ORDER BY input_index_in_epoch ASC + "#, + ) + .map_err(anyhow::Error::from)?; + + let rows = stmt + .query_map([u64_to_i64(epoch_number)], |r| r.get(0)) + .map_err(anyhow::Error::from)?; + + Ok(rows + .collect::>>() + .map_err(anyhow::Error::from)?) + }) + } + + pub fn last_input(&mut self) -> Result> { + self.read(last_input_in) + } + + /// How many inputs an epoch holds: the fed-window count the + /// geometry needs, without materializing any payload. + pub fn input_count(&mut self, epoch_number: u64) -> Result { + self.read(|tx| { + let count: i64 = tx + .query_row( + "SELECT COUNT(*) FROM inputs WHERE epoch_number = ?1", + params![u64_to_i64(epoch_number)], + |row| row.get(0), + ) + .map_err(anyhow::Error::from)?; + Ok(i64_to_u64(count)) + }) + } + + /// The resume point: the coordinate of the newest snapshot + /// boundary, whose input is the next to process. + pub fn next_input_id(&mut self) -> Result { + self.read(|tx| { + let (_, epoch_number, input_index_in_epoch, _) = + super::snapshots::latest_boundary_in(tx)?; + Ok(InputId { + epoch_number, + input_index_in_epoch, + }) + }) + } + + pub fn settlement_info(&mut self, epoch_number: u64) -> Result> { + self.read(|tx| settlement_info_in(tx, epoch_number)) + } +} + +// +// Transaction-level readers shared across roles +// + +pub(super) fn epoch_count_in(tx: &Transaction) -> Result { + let max: Option = tx + .query_row("SELECT MAX(epoch_number) FROM epochs", [], |row| row.get(0)) + .map_err(anyhow::Error::from)?; + Ok(max.map(|x| i64_to_u64(x) + 1).unwrap_or(0)) +} + +pub(super) fn last_input_in(tx: &Transaction) -> Result> { + let mut stmt = tx + .prepare_cached( + r#" + SELECT epoch_number, input_index_in_epoch FROM inputs + ORDER BY epoch_number DESC, input_index_in_epoch DESC + LIMIT 1 + "#, + ) + .map_err(anyhow::Error::from)?; + + Ok(stmt + .query_row([], |row| { + Ok(InputId { + epoch_number: i64_to_u64(row.get(0)?), + input_index_in_epoch: i64_to_u64(row.get(1)?), + }) + }) + .optional() + .map_err(anyhow::Error::from)?) +} + +pub(super) fn settlement_info_in( + tx: &Transaction, + epoch_number: u64, +) -> Result> { + let mut stmt = tx + .prepare_cached( + r#" + SELECT computation_hash, output_merkle, output_proof, final_state + FROM settlement_info + WHERE epoch_number = ?1 + "#, + ) + .map_err(anyhow::Error::from)?; + + let row = stmt + .query_row(params![u64_to_i64(epoch_number)], |row| { + Ok(( + row.get::<_, Vec>(0)?, + row.get::<_, Vec>(1)?, + row.get::<_, Vec>(2)?, + row.get::<_, Vec>(3)?, + )) + }) + .optional() + .map_err(anyhow::Error::from)?; + + row.map( + |(computation_hash, output_merkle, output_proof, final_state)| { + Ok(Settlement { + computation_hash: super::convert::blob_to_digest(computation_hash)?, + final_state: blob_to_hash(final_state)?, + output_merkle: blob_to_hash(output_merkle)?, + output_proof: Proof::from_flattened(output_proof)?, + }) + }, + ) + .transpose() +} + +fn row_to_epoch(row: &rusqlite::Row) -> rusqlite::Result> { + let tournament_str: String = row.get(2)?; + let epoch_number: i64 = row.get(0)?; + let input_index_boundary: i64 = row.get(1)?; + let block_created_number: i64 = row.get(3)?; + + Ok(Address::from_hex(&tournament_str) + .map_err(|e| { + StorageError::InnerError(anyhow::anyhow!( + "stored tournament address `{tournament_str}` is invalid: {e}" + )) + }) + .map(|root_tournament| Epoch { + epoch_number: i64_to_u64(epoch_number), + input_index_boundary: i64_to_u64(input_index_boundary), + root_tournament, + block_created_number: i64_to_u64(block_created_number), + })) +} diff --git a/cartesi-rollups/node/src/storage/rollups_machine.rs b/cartesi-rollups/node/src/storage/rollups_machine.rs new file mode 100644 index 000000000..1f4f2dabc --- /dev/null +++ b/cartesi-rollups/node/src/storage/rollups_machine.rs @@ -0,0 +1,169 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use std::path::Path; + +use crate::engine::constants::{ + LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, +}; + +use crate::storage::Proof; +use cartesi_machine::{ + config::runtime::RuntimeConfig, + constants::{ar::TX_START, machine::HASH_TREE_LOG2_ROOT_SIZE}, + error::MachineResult, + machine::Machine, + types::{Hash, SharingMode}, +}; + +// gap of each leaf in the commitment tree, should use the same value as ArbitrationConstants.sol:log2step(0) +pub const LOG2_STRIDE: u64 = 44; + +/// Level-0 leaves in one input window; also the height of a window's +/// subtree, making (epoch, LOG2_STRIDE, this, window) the canonical +/// quartet coordinate of a window root. +pub const LOG2_STRIDE_COUNT_IN_INPUT: u64 = + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE; + +pub const STRIDE_COUNT_IN_INPUT: u64 = 1 << LOG2_STRIDE_COUNT_IN_INPUT; + +pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 + << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH + - LOG2_STRIDE); + +/// The canonical quartet coordinate of a window's final level-0 +/// subtree root: one ordinary cache row per input, written by the +/// open regime as the window closes. Final by the frontier rule - +/// the window lies entirely left of the input frontier +/// (docs/plans/sling-design.md, the increment-E note). +pub fn window_root_quartet(epoch: u64, window: u64) -> crate::engine::Quartet { + crate::engine::Quartet { + epoch, + log2_stride: LOG2_STRIDE, + height: LOG2_STRIDE_COUNT_IN_INPUT, + shift: alloy::primitives::U256::from(window), + } +} + +pub struct RollupsMachine { + /// None only between [`RollupsMachine::close`] and + /// [`RollupsMachine::reopen_shared`] - the chain-of-clones swap + /// window, where the old instance must be destroyed (releasing + /// its directory locks) before its directory can be cloned. + machine: Option, + epoch_number: u64, + next_input_index_in_epoch: u64, +} + +impl RollupsMachine { + pub fn new( + path: &Path, + epoch_number: u64, + next_input_index_in_epoch: u64, + ) -> MachineResult { + let runtime_config = RuntimeConfig::quiet_console(); + let machine = Machine::load(path, &runtime_config)?; + + Ok(Self { + machine: Some(machine), + epoch_number, + next_input_index_in_epoch, + }) + } + + /// Loads a working clone SHARING_ALL: the directory IS the live + /// state, mutated in place and exclusively locked until close. + pub(super) fn load_shared( + path: &Path, + epoch_number: u64, + next_input_index_in_epoch: u64, + ) -> MachineResult { + let machine = + Machine::load_with_sharing(path, &RuntimeConfig::quiet_console(), SharingMode::All)?; + + Ok(Self { + machine: Some(machine), + epoch_number, + next_input_index_in_epoch, + }) + } + + /// Destroys the machine instance, flushing the working clone and + /// releasing its locks; epoch and input bookkeeping survive the + /// swap. Every other method panics until reopen_shared. + pub(super) fn close(&mut self) { + self.machine = None; + } + + /// Reopens on a (new) working clone after close. + pub(super) fn reopen_shared(&mut self, path: &Path) -> MachineResult<()> { + assert!(self.machine.is_none(), "reopen requires a closed machine"); + self.machine = Some(Machine::load_with_sharing( + path, + &RuntimeConfig::quiet_console(), + SharingMode::All, + )?); + Ok(()) + } + + fn inner(&mut self) -> &mut Machine { + self.machine + .as_mut() + .expect("machine open (closed only inside the clone swap)") + } + + pub fn epoch(&self) -> u64 { + self.epoch_number + } + + pub fn next_input_index_in_epoch(&self) -> u64 { + self.next_input_index_in_epoch + } + + pub fn finish_epoch(&mut self) { + self.epoch_number += 1; + self.next_input_index_in_epoch = 0; + } + + pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { + let proof = self.inner().proof(TX_START, 5, HASH_TREE_LOG2_ROOT_SIZE)?; + let siblings = Proof::new(proof.sibling_hashes); + let output_merkle = self.inner().read_memory(TX_START, 32)?; + + assert_eq!(output_merkle.len(), 32); + Ok((output_merkle.try_into().unwrap(), siblings)) + } + + pub fn state_hash(&mut self) -> MachineResult { + self.inner().root_hash() + } + + pub fn increment_input(&mut self) { + self.next_input_index_in_epoch += 1; + } + + /// Moves the machine out for a window-sized engine collect (the + /// runner wraps it in the advance stf); the counterpart of + /// [`RollupsMachine::put_machine`]. Distinct from close(), which + /// drops the instance to release its directory. + pub(crate) fn take_machine(&mut self) -> Machine { + self.machine + .take() + .expect("machine open (taken only around a window collect)") + } + + /// Returns the machine after a window collect: the same instance + /// (accepted) or the boundary-restored one (reverted); the record + /// verbs swap the backing clone either way. + pub(crate) fn put_machine(&mut self, machine: Machine) { + assert!(self.machine.is_none(), "put requires a taken machine"); + self.machine = Some(machine); + } + + /// Plain machine store into `dir`. The boundary store owns the + /// staging discipline and the content-addressed naming + /// (storage/snapshots.rs); this is its raw write. + pub(super) fn store_dir(&mut self, dir: &Path) -> MachineResult<()> { + self.inner().store(dir) + } +} diff --git a/cartesi-rollups/node/src/storage/snapshots.rs b/cartesi-rollups/node/src/storage/snapshots.rs new file mode 100644 index 000000000..f8ef7c52e --- /dev/null +++ b/cartesi-rollups/node/src/storage/snapshots.rs @@ -0,0 +1,874 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The boundary store: the one component every machine store, load, +//! and clean in the node goes through (docs/plans/snapshots.md). +//! +//! Identity is the input boundary - "the machine before input k", +//! yielded with a pristine uarch - because the window-opening +//! transition is fused (checkpoint + delivery + first ustep, one +//! leaf): post-feed states have no meta-cycle coordinate and are +//! never stored. Under the (epoch, input) map sits a content- +//! addressed store (directories named by machine root hash), which +//! is what makes registration idempotent: identical states dedup, +//! store races are benign, and a recomputation that disagrees with +//! its row fails loudly (write-once cell semantics, enforced by the +//! schema triggers). +//! +//! Reads are best-effort floors: any stored point at or before the +//! target only shortens replays, so rows whose directories vanished +//! are skipped, and the epoch start is the guaranteed answer of last +//! resort. Provenance is the writer's contract: a row must name the +//! boundary its machine truly sits at; the quartet cache's collision +//! tripwire cross-checks resumed against replayed computation +//! wherever they overlap. +//! +//! The write side is filesystem-first, database-second: machines +//! land in the store via a staging directory and an atomic rename +//! (the commit point - a crash can never leave a partial directory +//! at a final path), rows commit after, and directories orphaned by +//! GC are removed only after the transaction that unreferenced them. +//! A crash can orphan a directory, never dangle a row. + +use super::convert::{blob_to_hash, i64_to_u64, u64_to_i64}; +use super::error::{Result, StorageError}; +use super::open::{Storage, snapshots_path}; +use super::rollups_machine::RollupsMachine; +use crate::engine::InputBoundary; + +use cartesi_machine::error::MachineError; +use cartesi_machine::machine::Machine; +use cartesi_machine::types::Hash; +use rusqlite::{OptionalExtension, Transaction, params}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU64, Ordering}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StoreError { + #[error(transparent)] + Machine(#[from] MachineError), + + #[error("Failed to cleanup partial store {fs_err}, caused by {machine_err}")] + Cleanup { + machine_err: MachineError, + fs_err: std::io::Error, + }, + + #[error("Failed to stage snapshot store: {0}")] + Staging(std::io::Error), +} + +// +// Stores +// + +impl Storage { + /// Stores the machine into the content-addressed path via a + /// staging directory and an atomic rename. The rename is the + /// commit point: a crash mid-store can never leave a partial + /// directory at the final path, so the exists() gate stays + /// trustworthy on resume. Stale staging directories (crash + /// leftovers) are swept before reuse. Registration in the + /// (epoch, input) map is the caller's transaction's business. + pub(super) fn store_boundary( + &self, + machine: &mut RollupsMachine, + ) -> std::result::Result<(PathBuf, Hash), StoreError> { + let state_hash = machine.state_hash()?; + let dest = self.stage_machine_store(&state_hash, |staging| machine.store_dir(staging))?; + Ok((dest, state_hash)) + } + + /// The dispute write-back: commits a raw machine sitting at an + /// input boundary - stored only when the content-addressed + /// directory is absent, then registered. The row's write-once + /// cell makes the whole verb idempotent AND a cross-regime + /// nondeterminism tripwire: a replay that reaches a boundary + /// regime 1 recorded must produce the identical hash or fail + /// loudly. Returns the committed directory (the caller's revert + /// point). + pub fn commit_boundary_machine( + &mut self, + epoch_number: u64, + input_number: u64, + state_hash: &Hash, + machine: &mut Machine, + ) -> Result { + let dest = self + .stage_machine_store(state_hash, |staging| machine.store(staging)) + .map_err(anyhow::Error::from)?; + self.insert_boundary(epoch_number, input_number, state_hash, &dest)?; + Ok(dest) + } + + /// The staging discipline shared by every machine store: write + /// into a uniquely named staging directory, then atomically + /// rename to the content-addressed path. The rename is the + /// commit point: a crash mid-store can never leave a partial + /// directory at the final path, so the exists() gate stays + /// trustworthy on resume. Staging names are unique per store + /// (never keyed by hash: the hero and the roll can store an + /// identical state concurrently); crash leftovers die in the + /// startup sweep. + fn stage_machine_store( + &self, + state_hash: &Hash, + store: impl FnOnce(&Path) -> cartesi_machine::error::MachineResult<()>, + ) -> std::result::Result { + let snapshots = snapshots_path(self.state_dir()); + let dest = machine_store_path(&snapshots, state_hash); + + if !dest.exists() { + static SEQ: AtomicU64 = AtomicU64::new(0); + let staging = snapshots.join(format!( + ".part-{}-{}", + std::process::id(), + SEQ.fetch_add(1, Ordering::Relaxed) + )); + + if let Err(machine_err) = store(&staging) { + // cleanup partial store before returning error. + let fs_status = std::fs::remove_dir_all(&staging); + + if let Err(fs_err) = fs_status { + return Err(StoreError::Cleanup { + machine_err, + fs_err, + }); + } else { + return Err(machine_err.into()); + } + } + + if let Err(rename_err) = std::fs::rename(&staging, &dest) { + // Content addressing makes a lost race benign: an + // existing destination has identical content. + if dest.exists() { + let _ = std::fs::remove_dir_all(&staging); + } else { + return Err(StoreError::Staging(rename_err)); + } + } + } + + Ok(dest) + } + + /// A writable working clone of a stored machine, in the store's + /// staging namespace: scratch until committed, invisible to rows, + /// swept at startup if orphaned by a crash. Cheap where the + /// filesystem reflinks; a sparse copy elsewhere. + pub(super) fn checkout(&self, from: &Path) -> std::result::Result { + static SEQ: AtomicU64 = AtomicU64::new(0); + let working = snapshots_path(self.state_dir()).join(format!( + ".work-{}-{}", + std::process::id(), + SEQ.fetch_add(1, Ordering::Relaxed) + )); + Machine::clone_stored(from, &working)?; + Ok(working) + } + + /// Commits a working clone as the machine's content-addressed + /// directory: the atomic rename is the commit point, and an + /// already-existing destination has identical content (the CAS + /// dedup - rejected inputs, idle stretches), so the clone is + /// simply discarded against it. The caller must have closed the + /// machine first. Row registration is separate, as everywhere. + pub(super) fn commit_clone( + &self, + working: PathBuf, + state_hash: &Hash, + ) -> std::result::Result { + let dest = machine_store_path(&snapshots_path(self.state_dir()), state_hash); + if dest.exists() { + std::fs::remove_dir_all(&working).map_err(StoreError::Staging)?; + } else if let Err(rename_err) = std::fs::rename(&working, &dest) { + return Err(StoreError::Staging(rename_err)); + } + Ok(dest) + } + + /// Removes an abandoned working clone (a poisoned post-reject + /// state, or the spare clone a finished batch leaves behind). + pub(super) fn discard_clone(&self, working: &Path) -> std::result::Result<(), StoreError> { + std::fs::remove_dir_all(working).map_err(StoreError::Staging) + } + + /// Startup ritual: removes staging leftovers a crash orphaned - + /// partial stores (.part-*) and working clones (.work-*). Runs + /// before any worker holds a clone; never touches committed + /// directories. + pub fn sweep_stale_staging(&self) -> Result<()> { + let snapshots = snapshots_path(self.state_dir()); + let entries = match std::fs::read_dir(&snapshots) { + Ok(entries) => entries, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(e) => return Err(anyhow::Error::from(e).into()), + }; + for entry in entries.flatten() { + let name = entry.file_name(); + let Some(name) = name.to_str() else { continue }; + if (name.starts_with(".part-") || name.starts_with(".work-")) + && let Err(e) = std::fs::remove_dir_all(entry.path()) + && e.kind() != std::io::ErrorKind::NotFound + { + log::warn!( + "stale staging `{}` not removed: {e}", + entry.path().display() + ); + } + } + Ok(()) + } + + /// Registers a stored machine directory as the boundary before + /// `input_number` of `epoch_number`, in its own transaction. + /// Idempotent by the write-once cell: an identical registration + /// absorbs, a disagreeing hash or path fails loudly (schema + /// triggers). Callers never check before writing. + pub fn insert_boundary( + &mut self, + epoch_number: u64, + input_number: u64, + state_hash: &Hash, + dir: &Path, + ) -> Result<()> { + self.write(|tx| insert_snapshot_in(tx, epoch_number, input_number, state_hash, dir)) + } +} + +// +// Loads +// + +impl Storage { + /// The nearest stored boundary at or before `input` of the epoch, + /// skipping rows whose directories vanished (a missing snapshot + /// only lengthens a replay, never wrongs it). The epoch start is + /// the floor; its absence is an error, not a miss. + pub fn nearest_boundary_at_or_before( + &mut self, + epoch_number: u64, + input: u64, + ) -> Result<(InputBoundary, PathBuf)> { + let candidates = self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT e.input_number, s.file_path + FROM epoch_snapshot_info AS e + JOIN machine_state_snapshots AS s + ON s.state_hash = e.state_hash + WHERE e.epoch_number = ?1 AND e.input_number <= ?2 + ORDER BY e.input_number DESC + "#, + ) + .map_err(anyhow::Error::from)?; + + let rows = stmt + .query_map( + params![u64_to_i64(epoch_number), u64_to_i64(input)], + |row| { + Ok(( + InputBoundary(i64_to_u64(row.get::<_, i64>(0)?)), + PathBuf::from(row.get::<_, String>(1)?), + )) + }, + ) + .map_err(anyhow::Error::from)?; + + Ok(rows + .collect::>>() + .map_err(anyhow::Error::from)?) + })?; + + candidates + .into_iter() + .find(|(_, path)| path.exists()) + .ok_or_else(|| StorageError::DataNotFound { + description: format!( + "no stored boundary at or before input {input} of epoch {epoch_number} \ + (the epoch start is the guaranteed floor)" + ), + }) + } + + /// Loads the machine stored at a boundary, positioned to process + /// that boundary's input next. + pub fn snapshot( + &mut self, + epoch_number: u64, + input_number: u64, + ) -> Result> { + let path = self.snapshot_dir(epoch_number, input_number)?; + let ret = if let Some(path) = path { + Some(RollupsMachine::new(&path, epoch_number, input_number)?) + } else { + None + }; + + Ok(ret) + } + + pub fn snapshot_dir( + &mut self, + epoch_number: u64, + input_number: u64, + ) -> Result> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT s.file_path + FROM epoch_snapshot_info AS e + JOIN machine_state_snapshots AS s + ON s.state_hash = e.state_hash + WHERE e.epoch_number = ?1 AND e.input_number = ?2 + "#, + ) + .map_err(anyhow::Error::from)?; + + Ok(stmt + .query_row( + params![u64_to_i64(epoch_number), u64_to_i64(input_number)], + |row| row.get::<_, String>(0), + ) + .optional() + .map_err(anyhow::Error::from)? + .map(PathBuf::from)) + }) + } + + /// The state hash a boundary row names: the CAS key, and for the + /// epoch start the level-0 implicit hash. Trusting the row spares + /// a whole machine load where only the hash is needed. + pub fn snapshot_hash(&mut self, epoch_number: u64, input_number: u64) -> Result> { + self.read(|tx| { + let row = tx + .prepare_cached( + r#" + SELECT state_hash FROM epoch_snapshot_info + WHERE epoch_number = ?1 AND input_number = ?2 + "#, + ) + .map_err(anyhow::Error::from)? + .query_row( + params![u64_to_i64(epoch_number), u64_to_i64(input_number)], + |row| row.get::<_, Vec>(0), + ) + .optional() + .map_err(anyhow::Error::from)?; + + row.map(blob_to_hash).transpose() + }) + } + + pub fn latest_snapshot(&mut self) -> Result { + let (path, epoch_number, input_number, _) = self.read(latest_boundary_in)?; + Ok(RollupsMachine::new(&path, epoch_number, input_number)?) + } + + /// All surviving snapshot boundaries of an epoch, ordered by + /// input. Boundaries are typed: a stored machine sits yielded at + /// the input window it names. + pub fn epoch_snapshots(&mut self, epoch_number: u64) -> Result> { + self.read(|tx| { + let mut stmt = tx + .prepare_cached( + r#" + SELECT e.input_number, s.file_path + FROM epoch_snapshot_info AS e + JOIN machine_state_snapshots AS s + ON s.state_hash = e.state_hash + WHERE e.epoch_number = ?1 + ORDER BY e.input_number ASC + "#, + ) + .map_err(anyhow::Error::from)?; + + let rows = stmt + .query_map([u64_to_i64(epoch_number)], |row| { + Ok(( + InputBoundary(i64_to_u64(row.get::<_, i64>(0)?)), + PathBuf::from(row.get::<_, String>(1)?), + )) + }) + .map_err(anyhow::Error::from)?; + + Ok(rows + .collect::>>() + .map_err(anyhow::Error::from)?) + }) + } +} + +/// The newest snapshot boundary: (path, epoch, input, state_hash) - +/// the machine runner's resume point. At least one exists from the +/// migration's epoch-0 seed; its absence means a foreign or torn +/// database. +pub(super) fn latest_boundary_in(tx: &Transaction) -> Result<(PathBuf, u64, u64, Hash)> { + let mut stmt = tx + .prepare_cached( + r#" + SELECT s.file_path, e.epoch_number, e.input_number, e.state_hash + FROM epoch_snapshot_info AS e + JOIN machine_state_snapshots AS s + ON s.state_hash = e.state_hash + ORDER BY e.epoch_number DESC, e.input_number DESC + LIMIT 1 + "#, + ) + .map_err(anyhow::Error::from)?; + + let row = stmt + .query_row([], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, i64>(1)?, + row.get::<_, i64>(2)?, + row.get::<_, Vec>(3)?, + )) + }) + .optional() + .map_err(anyhow::Error::from)?; + + let (path, epoch, input, hash) = row.ok_or_else(|| StorageError::DataNotFound { + description: "snapshot boundary (the migration seeds epoch 0)".into(), + })?; + + Ok(( + path.into(), + i64_to_u64(epoch), + i64_to_u64(input), + blob_to_hash(hash)?, + )) +} + +// +// Row registration (transaction-level, for the writer roles) +// + +/// Registers a stored machine and indexes it as a boundary. Both +/// inserts absorb identical replays; the schema triggers abort a +/// disagreeing hash or path. +pub(super) fn insert_snapshot_in( + tx: &Transaction, + epoch_number: u64, + input_number: u64, + state_hash: &Hash, + dest_dir: &Path, +) -> Result<()> { + let mut stmt = tx + .prepare_cached( + r#" + INSERT INTO machine_state_snapshots(state_hash, file_path) + VALUES(?1, ?2) + ON CONFLICT(state_hash) DO NOTHING + "#, + ) + .map_err(anyhow::Error::from)?; + stmt.execute(params![state_hash, dest_dir.to_string_lossy()]) + .map_err(anyhow::Error::from)?; + + let mut stmt = tx + .prepare_cached( + r#" + INSERT INTO epoch_snapshot_info(epoch_number, input_number, state_hash) + VALUES(?1, ?2, ?3) + ON CONFLICT(epoch_number, input_number) DO NOTHING + "#, + ) + .map_err(anyhow::Error::from)?; + stmt.execute(params![ + u64_to_i64(epoch_number), + u64_to_i64(input_number), + state_hash + ]) + .map_err(anyhow::Error::from)?; + + Ok(()) +} + +pub(super) fn insert_template_machine_in(tx: &Transaction, state_hash: &Hash) -> Result<()> { + let mut stmt = tx + .prepare_cached( + r#" + INSERT OR IGNORE INTO template_machine (id, state_hash) + VALUES(1, ?1) + "#, + ) + .map_err(anyhow::Error::from)?; + stmt.execute(params![state_hash]) + .map_err(anyhow::Error::from)?; + + Ok(()) +} + +// +// Garbage collection +// + +/// Garbage-collects the epoch's intermediate boundaries, keeping the +/// epoch start, the anchor (the latest), and every snapshot_gap-th +/// input boundary - the preemptive material dispute replays resume +/// from (docs/plans/sling-design.md, increment D). A gap of 1 keeps +/// everything. Returns the directories orphaned by the sweep; the +/// caller removes them after the transaction commits. +pub(super) fn gc_previous_advances_in( + tx: &Transaction, + epoch: u64, + input_anchor: u64, + snapshot_gap: u64, +) -> Result> { + assert!(snapshot_gap >= 1, "a zero gap keeps nothing to divide by"); + tx.execute( + r#" + DELETE FROM epoch_snapshot_info + WHERE epoch_number = ?1 AND (input_number != ?2 AND input_number != 0) + AND (input_number % ?3) != 0 + "#, + params![ + u64_to_i64(epoch), + u64_to_i64(input_anchor), + u64_to_i64(snapshot_gap) + ], + ) + .map_err(anyhow::Error::from)?; + + sweep_unreferenced_snapshots_in(tx) +} + +/// Deletes content-addressed rows nothing references, returning their +/// directories. The FK RESTRICT on template_machine and +/// epoch_snapshot_info backs the NOT IN exclusions. +pub(super) fn sweep_unreferenced_snapshots_in(tx: &Transaction) -> Result> { + let mut stmt = tx + .prepare_cached( + r#" + DELETE FROM machine_state_snapshots + WHERE state_hash NOT IN ( + SELECT state_hash FROM epoch_snapshot_info + UNION + SELECT state_hash FROM template_machine + ) + RETURNING file_path + "#, + ) + .map_err(anyhow::Error::from)?; + + let rows = stmt + .query_map([], |row| row.get::<_, String>(0)) + .map_err(anyhow::Error::from)?; + + Ok(rows + .collect::>>() + .map_err(anyhow::Error::from)? + .into_iter() + .map(PathBuf::from) + .collect()) +} + +impl Storage { + /// Removes settled epochs' scratch directories (dispute work + /// under `state_dir//`), the filesystem sibling of + /// gc_old_epochs_in and safe by the same argument: with the + /// machine at epoch M, epochs at or below M - 2 belong to + /// settled disputes. The roll path sweeps as epochs settle; this + /// entry point is the startup ritual's, catching dirs a crash or + /// an older node version left behind. + pub fn sweep_settled_epoch_scratch(&mut self) -> Result<()> { + let machine_epoch = self.next_input_id()?.epoch_number; + if let Some(max_settled) = machine_epoch.checked_sub(2) { + sweep_scratch_dirs_at_or_below(self.state_dir(), max_settled); + } + Ok(()) + } +} + +/// Best effort: a surviving scratch dir costs disk, never +/// correctness, and the next sweep retries it. +pub(super) fn sweep_scratch_dirs_at_or_below(state_dir: &Path, max_epoch: u64) { + let Ok(entries) = std::fs::read_dir(state_dir) else { + return; + }; + for entry in entries.flatten() { + let name = entry.file_name(); + let Some(epoch) = name.to_str().and_then(|s| s.parse::().ok()) else { + continue; // db, snapshots/, and anything else non-numeric + }; + if epoch <= max_epoch + && let Err(e) = std::fs::remove_dir_all(entry.path()) + && e.kind() != std::io::ErrorKind::NotFound + { + log::warn!( + "settled epoch scratch `{}` not removed: {e}", + entry.path().display() + ); + } + } +} + +/// Best-effort, strictly after the commit that unreferenced the rows: +/// a failure leaves an orphan directory (harmless, re-adopted by a +/// later identical store), never a row pointing at nothing. +pub(super) fn remove_orphan_dirs(paths: &[PathBuf]) { + for path in paths { + if let Err(e) = std::fs::remove_dir_all(path) + && e.kind() != std::io::ErrorKind::NotFound + { + log::warn!("orphan snapshot dir `{}` not removed: {e}", path.display()); + } + } +} + +fn machine_store_path(snapshots_path: &Path, state_hash: &Hash) -> PathBuf { + snapshots_path.join(format!("0x{}", hex::encode(state_hash))) +} + +#[cfg(test)] +mod tests { + use super::super::sql::test_helper::setup_storage; + use super::*; + + #[test] + fn insert_snapshot_and_latest_boundary() { + let (_handle, mut s) = setup_storage(); + let dir = tempfile::TempDir::new().unwrap(); + + s.insert_boundary(42, 2, &[1u8; 32], dir.path()).unwrap(); + + let id = s.next_input_id().unwrap(); + assert_eq!(id.epoch_number, 42); + assert_eq!(id.input_index_in_epoch, 2); + + assert_eq!(s.snapshot_dir(42, 2).unwrap().unwrap(), dir.path()); + assert!(s.snapshot_dir(99, 99).unwrap().is_none()); + assert_eq!(s.snapshot_hash(42, 2).unwrap().unwrap(), [1u8; 32]); + assert!(s.snapshot_hash(99, 99).unwrap().is_none()); + } + + /// Idempotence: an identical registration absorbs. + #[test] + fn insert_boundary_absorbs_identical() { + let (_handle, mut s) = setup_storage(); + let dir = tempfile::TempDir::new().unwrap(); + + s.insert_boundary(7, 3, &[5u8; 32], dir.path()).unwrap(); + s.insert_boundary(7, 3, &[5u8; 32], dir.path()).unwrap(); + } + + /// A disagreeing hash for the same boundary is nondeterminism: + /// the write-once cell PANICS (the tick loops retry errors + /// forever; only a panic reaches the node's loud exit). + #[test] + #[should_panic(expected = "tripwire")] + fn insert_boundary_disagreement_panics() { + let (_handle, mut s) = setup_storage(); + let dir = tempfile::TempDir::new().unwrap(); + + s.insert_boundary(7, 3, &[5u8; 32], dir.path()).unwrap(); + let _ = s.insert_boundary(7, 3, &[6u8; 32], dir.path()); + } + + /// The dispute write-back verb: stores only when the CAS misses, + /// absorbs identical recommits, and fails loudly on a divergent + /// hash for a written boundary (the cross-regime tripwire). + #[test] + fn commit_boundary_machine_is_idempotent_and_loud() { + let (_handle, mut s) = setup_storage(); + let template = s.snapshot_dir(0, 0).unwrap().unwrap(); + let mut machine = cartesi_machine::machine::Machine::load( + &template, + &cartesi_machine::config::runtime::RuntimeConfig::quiet_console(), + ) + .unwrap(); + let hash = machine.root_hash().unwrap(); + + // The CAS already holds this state (it is the template), so + // the commit only registers the row. + let dest = s + .commit_boundary_machine(7, 3, &hash, &mut machine) + .unwrap(); + assert_eq!(dest, template); + assert_eq!(s.snapshot_dir(7, 3).unwrap().unwrap(), dest); + + // An identical recommit absorbs. + s.commit_boundary_machine(7, 3, &hash, &mut machine) + .unwrap(); + + // A boundary whose state the CAS misses gets stored: a real + // machine store lands under the new key. + let mut other = hash; + other[0] ^= 0xFF; + let dest2 = s + .commit_boundary_machine(7, 4, &other, &mut machine) + .unwrap(); + assert_ne!(dest2, dest); + assert!(dest2.join("config.json").exists()); + } + + /// A divergent hash for a written boundary is nondeterminism (the + /// cross-regime tripwire): PANIC, never a retryable error. + #[test] + #[should_panic(expected = "tripwire")] + fn commit_boundary_machine_divergence_panics() { + let (_handle, mut s) = setup_storage(); + let template = s.snapshot_dir(0, 0).unwrap().unwrap(); + let mut machine = cartesi_machine::machine::Machine::load( + &template, + &cartesi_machine::config::runtime::RuntimeConfig::quiet_console(), + ) + .unwrap(); + let hash = machine.root_hash().unwrap(); + s.commit_boundary_machine(7, 3, &hash, &mut machine) + .unwrap(); + + let mut divergent = hash; + divergent[0] ^= 0xFF; + let _ = s.commit_boundary_machine(7, 3, &divergent, &mut machine); + } + + /// The floor query answers the nearest surviving boundary, + /// skipping rows whose directories vanished. + #[test] + fn nearest_boundary_answers_the_floor_and_self_heals() { + let (_handle, mut s) = setup_storage(); + let kept: Vec = + (0..3).map(|_| tempfile::TempDir::new().unwrap()).collect(); + let vanishing = tempfile::TempDir::new().unwrap(); + + s.insert_boundary(5, 0, &[1u8; 32], kept[0].path()).unwrap(); + s.insert_boundary(5, 10, &[2u8; 32], kept[1].path()) + .unwrap(); + s.insert_boundary(5, 20, &[3u8; 32], vanishing.path()) + .unwrap(); + s.insert_boundary(5, 30, &[4u8; 32], kept[2].path()) + .unwrap(); + + let expect = |s: &mut Storage, input: u64, at: u64, path: &Path| { + let (b, d) = s.nearest_boundary_at_or_before(5, input).unwrap(); + assert_eq!((b, d.as_path()), (InputBoundary(at), path)); + }; + expect(&mut s, 0, 0, kept[0].path()); + expect(&mut s, 9, 0, kept[0].path()); + expect(&mut s, 10, 10, kept[1].path()); + expect(&mut s, 25, 20, vanishing.path()); + expect(&mut s, 1 << 23, 30, kept[2].path()); + + // Boundary 20's directory vanishes; its row is skipped and + // the floor falls back to 10. + drop(vanishing); + expect(&mut s, 25, 10, kept[1].path()); + + // A different epoch has no floor at all. + assert!(s.nearest_boundary_at_or_before(6, 100).is_err()); + } + + #[test] + fn gc_previous_advances_keeps_gap_boundaries() { + let (_handle, mut s) = setup_storage(); + let epoch = 5u64; + + let survivors = |s: &mut Storage| -> Vec { + s.epoch_snapshots(epoch) + .unwrap() + .into_iter() + .map(|(boundary, _)| boundary.0) + .collect() + }; + + let dirs: Vec = + (0..10).map(|_| tempfile::TempDir::new().unwrap()).collect(); + for (input, dir) in dirs.iter().enumerate() { + let hash = [input as u8 + 1; 32]; + s.insert_boundary(epoch, input as u64, &hash, dir.path()) + .unwrap(); + } + + let orphans = s + .write(|tx| gc_previous_advances_in(tx, epoch, 9, 4)) + .unwrap(); + assert_eq!(survivors(&mut s), vec![0, 4, 8, 9]); + assert_eq!(orphans.len(), 6, "six boundaries fell to the gap"); + + // A gap larger than any input keeps only the start and anchor. + let (_handle2, mut s2) = setup_storage(); + let dirs2: Vec = + (0..4).map(|_| tempfile::TempDir::new().unwrap()).collect(); + for (input, dir) in dirs2.iter().enumerate() { + let hash = [input as u8 + 1; 32]; + s2.insert_boundary(epoch, input as u64, &hash, dir.path()) + .unwrap(); + } + s2.write(|tx| gc_previous_advances_in(tx, epoch, 2, 1000).map(|_| ())) + .unwrap(); + assert_eq!( + s2.epoch_snapshots(epoch) + .unwrap() + .into_iter() + .map(|(b, _)| b.0) + .collect::>(), + vec![0, 2] + ); + + // A gap of 1 keeps every boundary. + let (_handle3, mut s3) = setup_storage(); + let dirs3: Vec = + (0..5).map(|_| tempfile::TempDir::new().unwrap()).collect(); + for (input, dir) in dirs3.iter().enumerate() { + let hash = [input as u8 + 1; 32]; + s3.insert_boundary(epoch, input as u64, &hash, dir.path()) + .unwrap(); + } + s3.write(|tx| gc_previous_advances_in(tx, epoch, 4, 1).map(|_| ())) + .unwrap(); + assert_eq!( + s3.epoch_snapshots(epoch) + .unwrap() + .into_iter() + .map(|(b, _)| b.0) + .collect::>(), + vec![0, 1, 2, 3, 4] + ); + } + + /// Orphaned directories are removed only after the commit; the + /// returned paths are exactly the swept rows' directories. + #[test] + fn gc_returns_orphan_directories_for_post_commit_removal() { + let (_handle, mut s) = setup_storage(); + let keep = tempfile::TempDir::new().unwrap(); + let drop_ = tempfile::TempDir::new().unwrap(); + + s.insert_boundary(3, 0, &[1u8; 32], keep.path()).unwrap(); + s.insert_boundary(3, 1, &[2u8; 32], drop_.path()).unwrap(); + + let orphans = s + .write(|tx| gc_previous_advances_in(tx, 3, 2, 1000)) + .unwrap(); + assert_eq!(orphans, vec![drop_.path().to_path_buf()]); + } + + /// The scratch sweep takes numeric dirs at or below the settled + /// boundary and nothing else: newer epochs, the database, and the + /// snapshot store are untouchable. + #[test] + fn scratch_sweep_spares_live_epochs_and_non_epoch_entries() { + let dir = tempfile::tempdir().unwrap(); + for name in ["0", "1", "2", "5", "snapshots"] { + std::fs::create_dir(dir.path().join(name)).unwrap(); + } + std::fs::write(dir.path().join("db.sqlite3"), b"x").unwrap(); + + sweep_scratch_dirs_at_or_below(dir.path(), 2); + + for gone in ["0", "1", "2"] { + assert!(!dir.path().join(gone).exists(), "{gone} should be swept"); + } + for kept in ["5", "snapshots", "db.sqlite3"] { + assert!(dir.path().join(kept).exists(), "{kept} should survive"); + } + } +} diff --git a/cartesi-rollups/node/src/storage/sql/discipline.rs b/cartesi-rollups/node/src/storage/sql/discipline.rs new file mode 100644 index 000000000..64c6e10d8 --- /dev/null +++ b/cartesi-rollups/node/src/storage/sql/discipline.rs @@ -0,0 +1,381 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The storage discipline tests (docs/plans/node-refactor.md, +//! workstream 2): every write belongs to one of four mutation classes, +//! and the schema's trigger layer must refuse writes outside it even +//! when they arrive through a raw connection that bypasses the Rust +//! checks. Each test drives one trigger to its RAISE(ABORT). + +use rusqlite::{Connection, params}; + +/// A migrated schema on a raw connection - no machine image, no +/// genesis seeding; the trigger layer is pure DDL. +fn migrated_conn() -> (tempfile::TempDir, Connection) { + let dir = tempfile::tempdir().unwrap(); + let mut conn = Connection::open(dir.path().join("db.sqlite3")).unwrap(); + super::migrations::migrate_to_latest(&mut conn).unwrap(); + (dir, conn) +} + +fn expect_abort(result: rusqlite::Result, message_fragment: &str) { + let err = result.expect_err("the trigger should refuse this write"); + let text = err.to_string(); + assert!( + text.contains(message_fragment), + "expected abort containing `{message_fragment}`, got `{text}`" + ); +} + +// +// epochs: append-only, dense from 0 +// + +#[test] +fn epochs_refuse_gaps_updates_and_deletes() { + let (_dir, conn) = migrated_conn(); + let insert = "INSERT INTO epochs VALUES (?1, 0, '0x00', 0)"; + + expect_abort(conn.execute(insert, params![1]), "densely from 0"); + conn.execute(insert, params![0]).unwrap(); + conn.execute(insert, params![1]).unwrap(); + expect_abort(conn.execute(insert, params![3]), "densely from 0"); + + expect_abort( + conn.execute("UPDATE epochs SET block_created_number = 9", []), + "append-only", + ); + expect_abort(conn.execute("DELETE FROM epochs", []), "append-only"); +} + +// +// inputs: append-only, advancing per InputId::validate_next +// + +#[test] +fn inputs_refuse_non_contiguous_coordinates() { + let (_dir, conn) = migrated_conn(); + let insert = "INSERT INTO inputs VALUES (?1, ?2, x'00')"; + + // the first input of the database must open an epoch + expect_abort(conn.execute(insert, params![0, 1]), "validate_next"); + + conn.execute(insert, params![0, 0]).unwrap(); + conn.execute(insert, params![0, 1]).unwrap(); + + // a gap within the epoch + expect_abort(conn.execute(insert, params![0, 3]), "validate_next"); + // a later epoch must restart at 0 + expect_abort(conn.execute(insert, params![2, 1]), "validate_next"); + // going backwards + expect_abort(conn.execute(insert, params![0, 0]), "validate_next"); + + // skipping an inputless epoch is legal + conn.execute(insert, params![2, 0]).unwrap(); +} + +#[test] +fn inputs_refuse_updates_and_deletes() { + let (_dir, conn) = migrated_conn(); + conn.execute("INSERT INTO inputs VALUES (0, 0, x'00')", []) + .unwrap(); + expect_abort( + conn.execute("UPDATE inputs SET input = x'01'", []), + "append-only", + ); + expect_abort(conn.execute("DELETE FROM inputs", []), "append-only"); +} + +// +// latest_processed: monotonic watermark singleton +// + +#[test] +fn latest_processed_only_rises_and_never_disappears() { + let (_dir, conn) = migrated_conn(); + let update = "UPDATE latest_processed SET block = ?1 WHERE id = 1"; + + conn.execute(update, params![10]).unwrap(); + // equal is a no-op raise, not a violation + conn.execute(update, params![10]).unwrap(); + expect_abort(conn.execute(update, params![9]), "only rises"); + expect_abort( + conn.execute("DELETE FROM latest_processed", []), + "permanent singleton", + ); +} + +// +// settlement_info: write-once cell per epoch +// + +#[test] +fn settlement_info_is_write_once() { + let (_dir, conn) = migrated_conn(); + conn.execute( + "INSERT INTO settlement_info VALUES (0, x'00', x'01', x'02', x'03')", + [], + ) + .unwrap(); + expect_abort( + conn.execute("UPDATE settlement_info SET computation_hash = x'ff'", []), + "write-once", + ); + expect_abort( + conn.execute("DELETE FROM settlement_info", []), + "write-once", + ); +} + +// +// sling_config: write-once cell +// + +#[test] +fn sling_config_is_write_once() { + let (_dir, conn) = migrated_conn(); + conn.execute( + "INSERT INTO sling_config VALUES (0, 24, 27, 20, x'00', x'01', 'v')", + [], + ) + .unwrap(); + expect_abort( + conn.execute("UPDATE sling_config SET emulator_version = 'w'", []), + "write-once", + ); + expect_abort(conn.execute("DELETE FROM sling_config", []), "write-once"); +} + +// +// template_machine: write-once cell with verify on re-insert +// + +#[test] +fn template_machine_absorbs_identical_and_refuses_drift() { + let (_dir, conn) = migrated_conn(); + // satisfy the FK on machine_state_snapshots + conn.execute( + "INSERT INTO machine_state_snapshots VALUES (?1, '/a')", + params![[1u8; 32]], + ) + .unwrap(); + conn.execute( + "INSERT INTO machine_state_snapshots VALUES (?1, '/b')", + params![[2u8; 32]], + ) + .unwrap(); + + let insert = "INSERT OR IGNORE INTO template_machine VALUES (1, ?1)"; + conn.execute(insert, params![[1u8; 32]]).unwrap(); + // identical re-insert absorbed; INSERT OR IGNORE previously + // absorbed disagreement too - the trigger closes exactly that + conn.execute(insert, params![[1u8; 32]]).unwrap(); + expect_abort( + conn.execute(insert, params![[2u8; 32]]), + "disagrees with its stored row", + ); + expect_abort( + conn.execute( + "UPDATE template_machine SET state_hash = ?1", + params![[2u8; 32]], + ), + "write-once", + ); + expect_abort( + conn.execute("DELETE FROM template_machine", []), + "write-once", + ); +} + +// +// sling_nodes: the collision tripwire; prune stays legal +// + +#[test] +fn sling_nodes_collision_aborts_in_the_database_itself() { + let (_dir, conn) = migrated_conn(); + let insert = "INSERT INTO sling_nodes VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT DO NOTHING"; + + conn.execute(insert, params![0, 44, 3, [0u8; 32], [7u8; 32]]) + .unwrap(); + // determinism makes duplicates benign + conn.execute(insert, params![0, 44, 3, [0u8; 32], [7u8; 32]]) + .unwrap(); + // a disagreeing hash at the same coordinate is the loudest signal + expect_abort( + conn.execute(insert, params![0, 44, 3, [0u8; 32], [8u8; 32]]), + "node cache collision", + ); + expect_abort( + conn.execute("UPDATE sling_nodes SET hash = x'00'", []), + "write-once", + ); + // settled-epoch prune is the blessed delete + conn.execute("DELETE FROM sling_nodes WHERE epoch <= 0", []) + .unwrap(); +} + +// +// epoch_snapshot_info / machine_state_snapshots: prunable derived +// stores with write-once-verify coordinates +// + +#[test] +fn snapshot_index_verifies_replays_and_refuses_updates() { + let (_dir, conn) = migrated_conn(); + conn.execute( + "INSERT INTO machine_state_snapshots VALUES (?1, '/a')", + params![[1u8; 32]], + ) + .unwrap(); + let insert = "INSERT INTO epoch_snapshot_info VALUES (0, 0, ?1) + ON CONFLICT DO NOTHING"; + conn.execute(insert, params![[1u8; 32]]).unwrap(); + conn.execute(insert, params![[1u8; 32]]).unwrap(); + expect_abort( + conn.execute(insert, params![[9u8; 32]]), + "nondeterminism or corruption", + ); + expect_abort( + conn.execute("UPDATE epoch_snapshot_info SET input_number = 5", []), + "write-once", + ); +} + +#[test] +fn cas_rows_pin_their_path() { + let (_dir, conn) = migrated_conn(); + let insert = "INSERT INTO machine_state_snapshots VALUES (?1, ?2) + ON CONFLICT DO NOTHING"; + conn.execute(insert, params![[1u8; 32], "/a"]).unwrap(); + conn.execute(insert, params![[1u8; 32], "/a"]).unwrap(); + expect_abort( + conn.execute(insert, params![[1u8; 32], "/b"]), + "different path", + ); + expect_abort( + conn.execute("UPDATE machine_state_snapshots SET file_path = '/c'", []), + "write-once", + ); +} + +// +// tournament_events: prunable derived store, gated by its watermark +// (fold phase 2) +// + +#[test] +fn tournament_events_stay_behind_the_watermark_and_final() { + let (_dir, conn) = migrated_conn(); + let insert = "INSERT INTO tournament_events VALUES ('aa', ?1, 0, x'00')"; + + // No watermark row yet: nothing is finalized, nothing may land. + expect_abort( + conn.execute(insert, params![5]), + "outrun the finalized watermark", + ); + + conn.execute( + "INSERT INTO tournament_events_watermark VALUES ('aa', 10)", + [], + ) + .unwrap(); + conn.execute(insert, params![5]).unwrap(); + conn.execute(insert, params![10]).unwrap(); + expect_abort( + conn.execute(insert, params![11]), + "outrun the finalized watermark", + ); + + expect_abort( + conn.execute("UPDATE tournament_events SET raw_log = x'01'", []), + "final", + ); + // Prunable derived store: the settled-epoch GC deletes freely. + conn.execute( + "DELETE FROM tournament_events WHERE root_tournament = 'aa'", + [], + ) + .unwrap(); +} + +#[test] +fn tournament_events_watermark_only_rises() { + let (_dir, conn) = migrated_conn(); + conn.execute( + "INSERT INTO tournament_events_watermark VALUES ('aa', 10)", + [], + ) + .unwrap(); + conn.execute( + "UPDATE tournament_events_watermark SET finalized_block = 12 + WHERE root_tournament = 'aa'", + [], + ) + .unwrap(); + expect_abort( + conn.execute( + "UPDATE tournament_events_watermark SET finalized_block = 11 + WHERE root_tournament = 'aa'", + [], + ), + "only rises", + ); + // Pruned with its dispute. + conn.execute( + "DELETE FROM tournament_events_watermark WHERE root_tournament = 'aa'", + [], + ) + .unwrap(); +} + +/// The grep-level half of the taxonomy check (the plan accepts it as +/// such): across the storage module's Rust sources, the only SQL +/// UPDATEs are the two watermark raises, and the only DELETEs are the +/// GC statements. New mutations must either fit an existing class or +/// change this test alongside a schema trigger. +#[test] +fn mutation_taxonomy_holds_at_source_level() { + let storage_src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/storage"); + + let mut update_hits: Vec<(String, usize)> = Vec::new(); + let mut delete_hits: Vec<(String, usize)> = Vec::new(); + + for entry in std::fs::read_dir(&storage_src).unwrap() { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("rs") { + continue; + } + let name = path.file_name().unwrap().to_string_lossy().into_owned(); + let source = std::fs::read_to_string(&path).unwrap(); + + let updates = source.matches("UPDATE").count(); + if updates > 0 { + update_hits.push((name.clone(), updates)); + } + let deletes = source.matches("DELETE FROM").count(); + if deletes > 0 { + delete_hits.push((name, deletes)); + } + } + + update_hits.sort(); + delete_hits.sort(); + assert_eq!( + update_hits, + vec![("dispute.rs".to_string(), 1), ("ingest.rs".to_string(), 1)], + "the two watermark upserts (tournament events; latest processed block) \ + are the only UPDATEs in the storage module" + ); + assert_eq!( + delete_hits, + vec![ + ("advance.rs".to_string(), 4), + ("snapshots.rs".to_string(), 2) + ], + "the GC paths are the only DELETEs: the old-epoch boundary, \ + sling_nodes, and tournament-event prunes in advance.rs; the gap \ + prune and the unreferenced-snapshot sweep in the boundary store" + ); +} diff --git a/cartesi-rollups/node/src/storage/sql/migrations.rs b/cartesi-rollups/node/src/storage/sql/migrations.rs new file mode 100644 index 000000000..2ff16b50d --- /dev/null +++ b/cartesi-rollups/node/src/storage/sql/migrations.rs @@ -0,0 +1,50 @@ +use lazy_static::lazy_static; +use rusqlite::Connection; +use rusqlite_migration::{M, Migrations}; + +lazy_static! { + pub static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![ + M::up(include_str!("migrations.sql")), + // One engine (one-engine.md section 6, amended): the runs + // table died - the window-root row is the runner's only + // level-0 artifact. v1's DDL no longer creates it, but a + // store that ran the old v1 carries the table, its triggers, + // and its never-GC'd rows forever (user_version gates by + // number, not content); the explicit drop keeps every store + // identical to a fresh one. IF EXISTS makes it a no-op on + // fresh databases; SQLite drops the triggers with the table. + M::up("DROP TABLE IF EXISTS machine_state_hashes;"), + // Staged settlement (next/3.0 contracts): settlement_info + // gained final_state, the post-epoch machine state hash the + // node claims as a sentry and asserts at stage/accept. The + // column lives in v1's DDL, so for fresh stores this step is + // the idempotent no-op below; stores below v3 are refused in + // migrate_to_latest instead - see the comment there. + M::up( + "CREATE TABLE IF NOT EXISTS settlement_info ( + epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), + computation_hash BLOB NOT NULL, + output_merkle BLOB NOT NULL, + output_proof BLOB NOT NULL, + final_state BLOB NOT NULL + );", + ), + ]); +} + +/// Stores below v3 predate settlement_info.final_state. The value +/// cannot be backfilled (gc_old_epochs may have dropped the boundary +/// rows that held it) and a placeholder would trip the settlement +/// asserts as a false consensus alarm, so the upgrade refuses loudly +/// instead of wedging or alarming. Fresh stores initialize at the +/// current shape. +pub fn migrate_to_latest(conn: &mut Connection) -> anyhow::Result<()> { + let version: u32 = conn.query_row("PRAGMA user_version", [], |row| row.get(0))?; + anyhow::ensure!( + version == 0 || version >= 3, + "store schema v{version} predates settlement_info.final_state and cannot be \ + upgraded in place; wipe the state dir and let the node rebuild" + ); + MIGRATIONS.to_latest(conn)?; + Ok(()) +} diff --git a/cartesi-rollups/node/src/storage/sql/migrations.sql b/cartesi-rollups/node/src/storage/sql/migrations.sql new file mode 100644 index 000000000..ec9c7b3e4 --- /dev/null +++ b/cartesi-rollups/node/src/storage/sql/migrations.sql @@ -0,0 +1,354 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +CREATE TABLE IF NOT EXISTS settlement_info ( + epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), + computation_hash BLOB NOT NULL, + output_merkle BLOB NOT NULL, + output_proof BLOB NOT NULL, + final_state BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS epochs ( + epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), + input_index_boundary INTEGER NOT NULL, + root_tournament TEXT NOT NULL, + block_created_number INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS inputs ( + epoch_number INTEGER NOT NULL CHECK (epoch_number >= 0), + input_index_in_epoch INTEGER NOT NULL, + input BLOB NOT NULL, + PRIMARY KEY (epoch_number, input_index_in_epoch) +); + +CREATE TABLE IF NOT EXISTS latest_processed ( + id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), + block INTEGER NOT NULL CHECK (block >= 0) +); +INSERT OR IGNORE INTO latest_processed (id, block) + VALUES (1, 0); + +CREATE TABLE IF NOT EXISTS template_machine ( + id INTEGER PRIMARY KEY CHECK (id = 1), + state_hash BLOB NOT NULL + UNIQUE + REFERENCES machine_state_snapshots (state_hash) + ON DELETE RESTRICT +) WITHOUT ROWID; + +CREATE TABLE IF NOT EXISTS machine_state_snapshots ( + state_hash BLOB NOT NULL PRIMARY KEY, + file_path TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS epoch_snapshot_info ( + epoch_number INTEGER NOT NULL CHECK (epoch_number >= 0), + input_number INTEGER NOT NULL CHECK (input_number >= 0), + state_hash BLOB NOT NULL, + + PRIMARY KEY (epoch_number, input_number), + + FOREIGN KEY (state_hash) + REFERENCES machine_state_snapshots (state_hash) + ON UPDATE CASCADE + ON DELETE RESTRICT +); + +-- Snapshot directory removal happens in Rust, strictly AFTER the +-- transaction that unreferenced the rows commits (the GC deletes +-- return the orphaned paths). A trigger used to delete directories +-- mid-transaction, which inverted the crash invariant: a rollback or +-- a mid-statement crash restored rows whose directories were already +-- gone. The rule is: a crash may orphan a directory, never dangle a +-- row. + +-- The sling dispute schema: the quartet cache and its write-once +-- configuration (sling/config.rs). This migration is the only DDL +-- path; config::pin writes the row once after it runs. + +CREATE TABLE IF NOT EXISTS sling_config ( + id INTEGER PRIMARY KEY CHECK (id = 0), + log2_input_span INTEGER NOT NULL, + log2_barch_span INTEGER NOT NULL, + log2_uarch_span INTEGER NOT NULL, + app BLOB NOT NULL, + template_hash BLOB NOT NULL, + emulator_version TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS sling_nodes ( + epoch INTEGER NOT NULL, + log2_stride INTEGER NOT NULL, + height INTEGER NOT NULL, + shift BLOB NOT NULL, + hash BLOB NOT NULL, + PRIMARY KEY (epoch, log2_stride, height, shift) +) WITHOUT ROWID; + +-- The dispute event log (workstream 5, phase 2): raw chain logs of +-- every tournament the dispute discovered, persisted once FINALIZED, +-- keyed for replay in chain order (block, then log index). The tail +-- past the watermark is never stored - it is scratch, refetched each +-- tick; persisted events are final by definition, which is the whole +-- reorg stance. Raw logs (JSON) rather than decoded events keep +-- tournament/fold.rs's decode_event the one decode authority, the +-- same shape the chain-recording fixtures use. Prunable derived +-- store: refetchable from the chain, deleted with the settled epoch. +CREATE TABLE IF NOT EXISTS tournament_events ( + root_tournament TEXT NOT NULL, -- encode_hex, as epochs stores it + block_number INTEGER NOT NULL, + log_index INTEGER NOT NULL, + raw_log BLOB NOT NULL, + PRIMARY KEY (root_tournament, block_number, log_index) +) WITHOUT ROWID; + +-- Monotonic watermark: the highest finalized block whose events are +-- fully persisted for this dispute. Advances every tick, events or +-- not, so the live tail refetch stays bounded. +CREATE TABLE IF NOT EXISTS tournament_events_watermark ( + root_tournament TEXT NOT NULL PRIMARY KEY, + finalized_block INTEGER NOT NULL +) WITHOUT ROWID; + +-- The invariant layer (docs/plans/node-refactor.md, workstream 3). +-- +-- Every write belongs to one of four classes: append-only log, +-- write-once cell (equal rewrites absorbed, disagreements fatal), +-- monotonic watermark, or prunable derived store. The triggers below +-- make the database itself refuse writes outside that taxonomy, so +-- the discipline holds even against a buggy writer or a raw +-- connection. The Rust writer keeps its own checks; these are +-- defense-in-depth, not the primary line. + +-- epochs: append-only log, dense from 0 (mirrors insert_epochs). + +CREATE TRIGGER IF NOT EXISTS trg_epochs_dense +BEFORE INSERT ON epochs +FOR EACH ROW +WHEN NEW.epoch_number != (SELECT COALESCE(MAX(epoch_number) + 1, 0) FROM epochs) +BEGIN + SELECT RAISE(ABORT, 'epochs must be inserted densely from 0'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_epochs_no_update +BEFORE UPDATE ON epochs +BEGIN + SELECT RAISE(ABORT, 'epochs is an append-only log'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_epochs_no_delete +BEFORE DELETE ON epochs +BEGIN + SELECT RAISE(ABORT, 'epochs is an append-only log'); +END; + +-- inputs: append-only log, advancing per InputId::validate_next - +-- next index within the last epoch, or index 0 in any later epoch +-- (epochs with no inputs are skipped, not padded). + +CREATE TRIGGER IF NOT EXISTS trg_inputs_contiguous +BEFORE INSERT ON inputs +FOR EACH ROW +WHEN NOT ( + (NOT EXISTS (SELECT 1 FROM inputs) AND NEW.input_index_in_epoch = 0) + OR EXISTS ( + SELECT 1 FROM ( + SELECT epoch_number AS e, input_index_in_epoch AS i + FROM inputs + ORDER BY epoch_number DESC, input_index_in_epoch DESC + LIMIT 1 + ) + WHERE (NEW.epoch_number = e AND NEW.input_index_in_epoch = i + 1) + OR (NEW.epoch_number > e AND NEW.input_index_in_epoch = 0) + ) +) +BEGIN + SELECT RAISE(ABORT, 'inputs must advance per InputId::validate_next'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_inputs_no_update +BEFORE UPDATE ON inputs +BEGIN + SELECT RAISE(ABORT, 'inputs is an append-only log'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_inputs_no_delete +BEFORE DELETE ON inputs +BEGIN + SELECT RAISE(ABORT, 'inputs is an append-only log'); +END; + +-- latest_processed: monotonic watermark on a permanent singleton. + +CREATE TRIGGER IF NOT EXISTS trg_latest_processed_monotone +BEFORE UPDATE OF block ON latest_processed +FOR EACH ROW +WHEN NEW.block < OLD.block +BEGIN + SELECT RAISE(ABORT, 'latest_processed only rises'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_latest_processed_no_delete +BEFORE DELETE ON latest_processed +BEGIN + SELECT RAISE(ABORT, 'latest_processed is a permanent singleton'); +END; + +-- settlement_info: write-once cell per epoch. + +CREATE TRIGGER IF NOT EXISTS trg_settlement_info_no_update +BEFORE UPDATE ON settlement_info +BEGIN + SELECT RAISE(ABORT, 'settlement_info is write-once per epoch'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_settlement_info_no_delete +BEFORE DELETE ON settlement_info +BEGIN + SELECT RAISE(ABORT, 'settlement_info is write-once per epoch'); +END; + +-- sling_config: write-once cell (config::pin absorbs an identical +-- re-pin and refuses drift in Rust; the triggers close the raw path). + +CREATE TRIGGER IF NOT EXISTS trg_sling_config_no_update +BEFORE UPDATE ON sling_config +BEGIN + SELECT RAISE(ABORT, 'sling_config is write-once'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_sling_config_no_delete +BEFORE DELETE ON sling_config +BEGIN + SELECT RAISE(ABORT, 'sling_config is write-once'); +END; + +-- template_machine: write-once cell. INSERT OR IGNORE previously +-- absorbed a DISAGREEING rewrite silently; the verify trigger closes +-- that (equal rewrites still absorb via the conflict clause). + +CREATE TRIGGER IF NOT EXISTS trg_template_machine_write_once_verify +BEFORE INSERT ON template_machine +FOR EACH ROW +WHEN EXISTS ( + SELECT 1 FROM template_machine + WHERE id = NEW.id AND state_hash != NEW.state_hash +) +BEGIN + SELECT RAISE(ABORT, 'template_machine disagrees with its stored row'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_template_machine_no_update +BEFORE UPDATE ON template_machine +BEGIN + SELECT RAISE(ABORT, 'template_machine is write-once'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_template_machine_no_delete +BEFORE DELETE ON template_machine +BEGIN + SELECT RAISE(ABORT, 'template_machine is write-once'); +END; + +-- sling_nodes: append-only write-once-verify (the nondeterminism +-- tripwire; message and semantics mirror Storage::insert_quartet_nodes) +-- plus settled-epoch prune (gc_old_epochs deletes epochs at least two +-- behind the live dispute - DaveConsensus settles epoch N before +-- sealing N + 1, so those tournaments are finished). + +CREATE TRIGGER IF NOT EXISTS trg_sling_nodes_collision +BEFORE INSERT ON sling_nodes +FOR EACH ROW +WHEN EXISTS ( + SELECT 1 FROM sling_nodes + WHERE epoch = NEW.epoch AND log2_stride = NEW.log2_stride + AND height = NEW.height AND shift = NEW.shift + AND hash != NEW.hash +) +BEGIN + SELECT RAISE(ABORT, 'node cache collision: nondeterminism or version drift'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_sling_nodes_no_update +BEFORE UPDATE ON sling_nodes +BEGIN + SELECT RAISE(ABORT, 'sling_nodes rows are write-once'); +END; + +-- epoch_snapshot_info: prunable derived store with write-once-verify +-- replay semantics on the boundary coordinate (a reprocessed boundary +-- must reproduce the same machine state). + +CREATE TRIGGER IF NOT EXISTS trg_epoch_snapshot_info_write_once_verify +BEFORE INSERT ON epoch_snapshot_info +FOR EACH ROW +WHEN EXISTS ( + SELECT 1 FROM epoch_snapshot_info + WHERE epoch_number = NEW.epoch_number + AND input_number = NEW.input_number + AND state_hash != NEW.state_hash +) +BEGIN + SELECT RAISE(ABORT, 'snapshot boundary disagrees with its stored row: nondeterminism or corruption'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_epoch_snapshot_info_no_update +BEFORE UPDATE ON epoch_snapshot_info +BEGIN + SELECT RAISE(ABORT, 'epoch_snapshot_info rows are write-once (prune-only)'); +END; + +-- machine_state_snapshots: content-addressed store; the path is a +-- pure function of the hash, so a re-registration at a different +-- path is corruption. + +CREATE TRIGGER IF NOT EXISTS trg_snapshots_cas_immutable +BEFORE INSERT ON machine_state_snapshots +FOR EACH ROW +WHEN EXISTS ( + SELECT 1 FROM machine_state_snapshots + WHERE state_hash = NEW.state_hash AND file_path != NEW.file_path +) +BEGIN + SELECT RAISE(ABORT, 'content-addressed snapshot re-registered at a different path'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_snapshots_no_update +BEFORE UPDATE ON machine_state_snapshots +BEGIN + SELECT RAISE(ABORT, 'machine_state_snapshots rows are write-once (prune-only)'); +END; + +-- tournament_events: prunable derived store (chain-refetchable, +-- deleted with the settled epoch); rows are final once written, and +-- nothing past a dispute's watermark may be stored - the tail is +-- scratch by design. + +CREATE TRIGGER IF NOT EXISTS trg_tournament_events_no_update +BEFORE UPDATE ON tournament_events +BEGIN + SELECT RAISE(ABORT, 'tournament_events rows are final (prune-only)'); +END; + +CREATE TRIGGER IF NOT EXISTS trg_tournament_events_finalized_only +BEFORE INSERT ON tournament_events +FOR EACH ROW +WHEN NEW.block_number > COALESCE(( + SELECT finalized_block FROM tournament_events_watermark + WHERE root_tournament = NEW.root_tournament +), -1) +BEGIN + SELECT RAISE(ABORT, 'tournament_events must not outrun the finalized watermark'); +END; + +-- tournament_events_watermark: monotonic; pruned with its dispute. + +CREATE TRIGGER IF NOT EXISTS trg_tournament_events_watermark_monotone +BEFORE UPDATE OF finalized_block ON tournament_events_watermark +FOR EACH ROW +WHEN NEW.finalized_block < OLD.finalized_block +BEGIN + SELECT RAISE(ABORT, 'tournament_events_watermark only rises'); +END; diff --git a/cartesi-rollups/node/src/storage/sql/mod.rs b/cartesi-rollups/node/src/storage/sql/mod.rs new file mode 100644 index 000000000..c8a6e893b --- /dev/null +++ b/cartesi-rollups/node/src/storage/sql/mod.rs @@ -0,0 +1,13 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The DDL and its guards: the single migration (one migration, one +//! DDL path) and the discipline tests that drive every schema +//! trigger to its abort. + +pub mod migrations; + +#[cfg(test)] +mod discipline; +#[cfg(test)] +pub(crate) mod test_helper; diff --git a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs b/cartesi-rollups/node/src/storage/sql/test_helper.rs similarity index 60% rename from cartesi-rollups/node/state-manager/src/sql/test_helper.rs rename to cartesi-rollups/node/src/storage/sql/test_helper.rs index d849c6b06..a9edd5573 100644 --- a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs +++ b/cartesi-rollups/node/src/storage/sql/test_helper.rs @@ -1,6 +1,7 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +use crate::storage::Storage; use cartesi_machine::{ Machine, config::{ @@ -8,12 +9,12 @@ use cartesi_machine::{ runtime::RuntimeConfig, }, }; -use rusqlite::Connection; use tempfile::{TempDir, tempdir}; -use super::migrate; - -pub fn setup_db() -> (TempDir, Connection) { +/// A fully migrated Storage over a real (tiny) machine image: the +/// production setup path, template snapshot and engine config +/// included. Tests need `../../test/programs/linux.bin` present. +pub fn setup_storage() -> (TempDir, Storage) { let state_dir_ = tempdir().unwrap(); let state_dir = state_dir_.path(); @@ -22,7 +23,7 @@ pub fn setup_db() -> (TempDir, Connection) { &MachineConfig::new_with_ram(RAMConfig { length: 134217728, backing_store: cartesi_machine::config::machine::BackingStoreConfig { - data_filename: "../../../test/programs/linux.bin".into(), + data_filename: "../../test/programs/linux.bin".into(), ..Default::default() }, }), @@ -31,6 +32,12 @@ pub fn setup_db() -> (TempDir, Connection) { .unwrap(); machine.store(&machine_path).unwrap(); - let conn = migrate(state_dir, &machine_path, 0).unwrap(); - (state_dir_, conn) + let storage = Storage::migrate( + state_dir, + &machine_path, + 0, + alloy::primitives::Address::ZERO, + ) + .unwrap(); + (state_dir_, storage) } diff --git a/cartesi-rollups/node/src/sync.rs b/cartesi-rollups/node/src/sync.rs new file mode 100644 index 000000000..600d56588 --- /dev/null +++ b/cartesi-rollups/node/src/sync.rs @@ -0,0 +1,140 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Shutdown as a primitive, not an error channel: anyone may request +//! shutdown, workers observe it, and that is the whole contract. +//! Worker errors do NOT travel through here - they return through +//! their JoinHandles, and the runtime loop in lib.rs turns the first +//! exit into a shutdown request for everyone else. (The predecessor, +//! Watch, carried the first error to every worker through a condvar; +//! that conflation is what this replaces.) + +use std::sync::{ + Arc, Condvar, Mutex, + atomic::{AtomicBool, Ordering}, +}; +use std::time::Duration; +use tokio::sync::Notify; + +#[derive(Debug, Clone, Default)] +pub struct ShutdownSignal { + inner: Arc, +} + +#[derive(Debug, Default)] +struct Inner { + requested: AtomicBool, + // async waiters (worker select loops) + notify: Notify, + // blocking waiters (the machine runner's sleep) + mutex: Mutex<()>, + condvar: Condvar, +} + +impl ShutdownSignal { + pub fn request(&self) { + self.inner.requested.store(true, Ordering::SeqCst); + self.inner.notify.notify_waiters(); + let _guard = self.inner.mutex.lock().unwrap(); + self.inner.condvar.notify_all(); + } + + pub fn is_requested(&self) -> bool { + self.inner.requested.load(Ordering::SeqCst) + } + + /// Resolves when shutdown is requested; immediately if it already + /// was. Cancel-safe: made for `select!` against the worker's tick + /// sleep. + pub async fn requested(&self) { + let mut notified = std::pin::pin!(self.inner.notify.notified()); + // enable() is the registration point (creating the future is + // not); registering before the flag check closes the race + // where a request lands between check and first poll and its + // notify_waiters reaches no one. One round suffices: the flag + // is set before the wake and never clears. + notified.as_mut().enable(); + if self.is_requested() { + return; + } + notified.await; + } + + /// Blocking sleep that a shutdown request cuts short. Returns + /// whether shutdown was requested. + pub fn wait_timeout(&self, duration: Duration) -> bool { + let guard = self.inner.mutex.lock().unwrap(); + let _unused = self + .inner + .condvar + .wait_timeout_while(guard, duration, |()| !self.is_requested()) + .unwrap(); + self.is_requested() + } +} + +#[cfg(test)] +mod tests { + use super::ShutdownSignal; + use std::thread; + use std::time::{Duration, Instant}; + + #[test] + fn fresh_signal_times_out() { + let s = ShutdownSignal::default(); + assert!(!s.wait_timeout(Duration::from_millis(10))); + assert!(!s.is_requested()); + } + + #[test] + fn request_cuts_blocking_wait_short() { + let s = ShutdownSignal::default(); + let s2 = s.clone(); + + let handle = thread::spawn(move || { + let t0 = Instant::now(); + assert!(s2.wait_timeout(Duration::from_secs(5))); + assert!(t0.elapsed() < Duration::from_millis(500)); + }); + + thread::sleep(Duration::from_millis(50)); + s.request(); + handle.join().unwrap(); + assert!(s.is_requested()); + } + + #[test] + fn request_is_idempotent_and_sticky() { + let s = ShutdownSignal::default(); + s.request(); + s.request(); + assert!(s.is_requested()); + // an already-requested signal does not sleep + let t0 = Instant::now(); + assert!(s.wait_timeout(Duration::from_secs(5))); + assert!(t0.elapsed() < Duration::from_millis(500)); + } + + #[tokio::test] + async fn async_waiter_wakes_on_request() { + let s = ShutdownSignal::default(); + let s2 = s.clone(); + + let waiter = tokio::spawn(async move { s2.requested().await }); + tokio::time::sleep(Duration::from_millis(50)).await; + s.request(); + tokio::time::timeout(Duration::from_secs(1), waiter) + .await + .expect("waiter wakes") + .unwrap(); + } + + #[tokio::test] + async fn async_waiter_resolves_immediately_when_already_requested() { + let s = ShutdownSignal::default(); + s.request(); + tokio::time::timeout(Duration::from_millis(100), s.requested()) + .await + .expect("resolves without waiting"); + } +} diff --git a/cartesi-rollups/node/src/tournament/fold.rs b/cartesi-rollups/node/src/tournament/fold.rs new file mode 100644 index 000000000..7f219170c --- /dev/null +++ b/cartesi-rollups/node/src/tournament/fold.rs @@ -0,0 +1,579 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Tournament state = fold(events): the pure half of the tournament +//! reader (docs/plans/node-refactor.md, workstream 5). +//! +//! The chain's event vocabulary fully determines the dispute's +//! STRUCTURE: which tournaments exist (inner ones are discovered by +//! the fold itself), which commitments joined with which final +//! states, which matches were created, sealed into inner tournaments, +//! or deleted with which winner. The fold derives exactly that, from +//! genesis, every tick - ticks are seconds apart and a full dispute +//! is hundreds of events, so compute is nothing, no derived state is +//! ever persisted, and cold start equals tick. +//! +//! What events deliberately do NOT determine stays out of the fold +//! and in the per-tick point-read overlay: +//! +//! - Live match positions (runningLeafPosition, currentHeight, +//! otherParent/leftNode): MatchAdvanced names the new otherParent, +//! but when a node's children are equal hashes (real in padded +//! regions) the descent direction is ambiguous from events alone, +//! and the position depends on it. The contract knows; getMatch is +//! the authority. +//! - Clocks: every Clock mutation is deterministic in (state, block), +//! but MatchAdvanced does not name the mover, so allowances are not +//! attributable from events alone. getCommitment is the authority. +//! - Winners and elimination: arbitrationResult, +//! innerTournamentWinner, and canBeEliminated encode validity logic +//! the chain owns; MatchDeleted's winner is recorded as structure, +//! but the tournament-level verdict stays a point read. + +use std::collections::HashMap; + +use crate::merkle::Digest; +use alloy::{primitives::Address, rpc::types::Log, sol_types::SolEvent}; +use anyhow::{Result, anyhow, bail, ensure}; +use cartesi_prt_contracts::tournament::Tournament; + +use crate::tournament::MatchID; + +/// Why a match left the bracket (ITournament.MatchDeletionReason). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MatchDeletionReason { + Step, + Timeout, + ChildTournament, +} + +/// Which commitment survived a deleted match +/// (ITournament.WinnerCommitment). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WinnerCommitment { + Neither, + One, + Two, +} + +/// One tournament event, as the contracts emit it. Carries its +/// tournament address and block number; inner tournaments are +/// discovered by the fold (a NewInnerTournament names the address +/// whose log stream must also be fetched). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TournamentEvent { + pub tournament: Address, + pub block: u64, + pub kind: EventKind, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EventKind { + CommitmentJoined { + root: Digest, + final_state: Digest, + }, + MatchCreated { + one: Digest, + two: Digest, + left_of_two: Digest, + }, + MatchAdvanced { + match_id_hash: Digest, + other_parent: Digest, + left_node: Digest, + }, + MatchDeleted { + match_id_hash: Digest, + reason: MatchDeletionReason, + winner: WinnerCommitment, + }, + NewInnerTournament { + match_id_hash: Digest, + child: Address, + }, +} + +/// Decodes a raw chain log into a tournament event, through the same +/// bindings the fetcher uses. `None` for any log that is not one of +/// the tournament's five structural events (bond refunds and foreign +/// contracts' logs among them); the caller filters by address. +pub fn decode_event(log: &Log) -> Result> { + let tournament = log.address(); + let block = log + .block_number + .ok_or_else(|| anyhow!("chain log without a block number"))?; + let primitive = &log.inner; + + let kind = if let Ok(e) = Tournament::CommitmentJoined::decode_log(primitive) { + EventKind::CommitmentJoined { + root: e.commitment.into(), + final_state: e.finalStateHash.into(), + } + } else if let Ok(e) = Tournament::MatchCreated::decode_log(primitive) { + EventKind::MatchCreated { + one: e.one.into(), + two: e.two.into(), + left_of_two: e.leftOfTwo.into(), + } + } else if let Ok(e) = Tournament::MatchAdvanced::decode_log(primitive) { + EventKind::MatchAdvanced { + match_id_hash: e.matchIdHash.into(), + other_parent: e.otherParent.into(), + left_node: e.leftNode.into(), + } + } else if let Ok(e) = Tournament::MatchDeleted::decode_log(primitive) { + EventKind::MatchDeleted { + match_id_hash: e.matchIdHash.into(), + // The bindings model Solidity enums as u8 newtypes; the + // discriminants mirror ITournament.sol's declaration order. + reason: match e.reason { + 0 => MatchDeletionReason::Step, + 1 => MatchDeletionReason::Timeout, + 2 => MatchDeletionReason::ChildTournament, + other => bail!("unknown match deletion reason {other}"), + }, + winner: match e.winnerCommitment { + 0 => WinnerCommitment::Neither, + 1 => WinnerCommitment::One, + 2 => WinnerCommitment::Two, + other => bail!("unknown winner commitment {other}"), + }, + } + } else if let Ok(e) = Tournament::NewInnerTournament::decode_log(primitive) { + EventKind::NewInnerTournament { + match_id_hash: e.matchIdHash.into(), + child: e.childTournament, + } + } else { + return Ok(None); + }; + + Ok(Some(TournamentEvent { + tournament, + block, + kind, + })) +} + +/// A commitment's structural record. +#[derive(Debug, Clone, PartialEq)] +pub struct CommitmentFold { + pub root: Digest, + pub final_state: Digest, + pub joined_at_block: u64, + /// Index into the tournament's match list, most recent first. + pub latest_match: Option, +} + +/// A match's structural record, from creation to deletion. +#[derive(Debug, Clone, PartialEq)] +pub struct MatchFold { + pub id: MatchID, + pub created_at_block: u64, + /// MatchAdvanced count: how far the bisection has descended. + pub advances: u64, + /// The last event-reported (otherParent, leftNode). Structural + /// breadcrumbs only - live positioning reads the chain (see the + /// module doc on descent ambiguity). + pub last_other_parent: Digest, + pub last_left_node: Digest, + pub inner_tournament: Option
, + pub deleted: Option<(MatchDeletionReason, WinnerCommitment)>, +} + +impl MatchFold { + pub fn is_live(&self) -> bool { + self.deleted.is_none() + } +} + +/// A tournament's structural record. +#[derive(Debug, Clone, PartialEq)] +pub struct TournamentFold { + pub address: Address, + /// The parent tournament and the sealed match that spawned this + /// one; None for the root. + pub parent: Option<(Address, Digest)>, + /// Root is 0; each inner tournament is one deeper. + pub level: u64, + pub commitments: HashMap, + pub matches: Vec, + match_index: HashMap, +} + +impl TournamentFold { + fn new(address: Address, parent: Option<(Address, Digest)>, level: u64) -> Self { + TournamentFold { + address, + parent, + level, + commitments: HashMap::new(), + matches: Vec::new(), + match_index: HashMap::new(), + } + } + + pub fn match_by_id_hash(&self, id_hash: &Digest) -> Option<&MatchFold> { + self.match_index.get(id_hash).map(|i| &self.matches[*i]) + } + + pub fn live_matches(&self) -> impl Iterator { + self.matches.iter().filter(|m| m.is_live()) + } +} + +/// The pure fold. Feed it every event of every discovered tournament, +/// in block order per tournament; it derives the dispute's structure +/// and nothing else. Applying is fallible only against MALFORMED +/// streams (an advance for a match never created, a duplicate +/// creation): those mean a broken fetcher or a wrong address set, and +/// the fold refuses loudly rather than folding garbage. +#[derive(Debug, Clone, PartialEq)] +pub struct Fold { + root: Address, + tournaments: HashMap, + /// Discovery order: parents before children. + order: Vec
, +} + +impl Fold { + pub fn new(root: Address) -> Self { + let mut tournaments = HashMap::new(); + tournaments.insert(root, TournamentFold::new(root, None, 0)); + Fold { + root, + tournaments, + order: vec![root], + } + } + + pub fn root(&self) -> Address { + self.root + } + + /// Every discovered tournament, parents before children: the + /// fetch set. A fetch round that discovers a new inner tournament + /// must fetch its logs and fold again; depth is bounded by the + /// level count, so the loop closes in at most that many rounds. + pub fn addresses(&self) -> Vec
{ + self.order.clone() + } + + pub fn tournament(&self, address: &Address) -> Option<&TournamentFold> { + self.tournaments.get(address) + } + + pub fn tournaments(&self) -> impl Iterator { + self.order.iter().map(|a| &self.tournaments[a]) + } + + pub fn apply(&mut self, event: &TournamentEvent) -> Result<()> { + let tournament = self + .tournaments + .get_mut(&event.tournament) + .ok_or_else(|| anyhow!("event for undiscovered tournament {}", event.tournament))?; + + match &event.kind { + EventKind::CommitmentJoined { root, final_state } => { + // Rejoining is a chain-side impossibility; two events + // for one root mean a double-fetched range. + ensure!( + !tournament.commitments.contains_key(root), + "commitment {root} joined twice (double-fetched range?)" + ); + tournament.commitments.insert( + *root, + CommitmentFold { + root: *root, + final_state: *final_state, + joined_at_block: event.block, + latest_match: None, + }, + ); + } + + EventKind::MatchCreated { + one, + two, + left_of_two, + } => { + let id = MatchID { + commitment_one: *one, + commitment_two: *two, + }; + let id_hash = id.hash(); + ensure!( + !tournament.match_index.contains_key(&id_hash), + "match {id_hash} created twice (double-fetched range?)" + ); + let index = tournament.matches.len(); + tournament.matches.push(MatchFold { + id, + created_at_block: event.block, + advances: 0, + last_other_parent: *one, + last_left_node: *left_of_two, + inner_tournament: None, + deleted: None, + }); + tournament.match_index.insert(id_hash, index); + for commitment in [one, two] { + tournament + .commitments + .get_mut(commitment) + .ok_or_else(|| { + anyhow!("match created for unjoined commitment {commitment}") + })? + .latest_match = Some(index); + } + } + + EventKind::MatchAdvanced { + match_id_hash, + other_parent, + left_node, + } => { + let m = mutable_match(tournament, match_id_hash)?; + ensure!(m.is_live(), "advance on a deleted match {match_id_hash}"); + m.advances += 1; + m.last_other_parent = *other_parent; + m.last_left_node = *left_node; + } + + EventKind::MatchDeleted { + match_id_hash, + reason, + winner, + } => { + let m = mutable_match(tournament, match_id_hash)?; + ensure!(m.is_live(), "match {match_id_hash} deleted twice"); + m.deleted = Some((*reason, *winner)); + } + + EventKind::NewInnerTournament { + match_id_hash, + child, + } => { + let level = tournament.level; + let parent_address = tournament.address; + let m = mutable_match(tournament, match_id_hash)?; + ensure!( + m.inner_tournament.is_none(), + "match {match_id_hash} sealed twice" + ); + m.inner_tournament = Some(*child); + + ensure!( + !self.tournaments.contains_key(child), + "inner tournament {child} created twice" + ); + self.tournaments.insert( + *child, + TournamentFold::new(*child, Some((parent_address, *match_id_hash)), level + 1), + ); + self.order.push(*child); + } + } + + Ok(()) + } +} + +fn mutable_match<'a>( + tournament: &'a mut TournamentFold, + id_hash: &Digest, +) -> Result<&'a mut MatchFold> { + let index = *tournament + .match_index + .get(id_hash) + .ok_or_else(|| anyhow!("event for unknown match {id_hash}"))?; + Ok(&mut tournament.matches[index]) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn digest(byte: u8) -> Digest { + Digest::from_digest(&[byte; 32]).unwrap() + } + + fn address(byte: u8) -> Address { + Address::from([byte; 20]) + } + + fn event(tournament: Address, block: u64, kind: EventKind) -> TournamentEvent { + TournamentEvent { + tournament, + block, + kind, + } + } + + fn join(root: u8) -> EventKind { + EventKind::CommitmentJoined { + root: digest(root), + final_state: digest(root + 100), + } + } + + /// A two-commitment dispute descending into an inner tournament + /// and resolving: the whole structural vocabulary in one script. + #[test] + fn fold_derives_the_dispute_tree() { + let root = address(1); + let inner = address(2); + let mut fold = Fold::new(root); + + let id = MatchID { + commitment_one: digest(10), + commitment_two: digest(20), + }; + let id_hash = id.hash(); + + let script = [ + event(root, 5, join(10)), + event(root, 6, join(20)), + event( + root, + 7, + EventKind::MatchCreated { + one: digest(10), + two: digest(20), + left_of_two: digest(21), + }, + ), + event( + root, + 8, + EventKind::MatchAdvanced { + match_id_hash: id_hash, + other_parent: digest(21), + left_node: digest(11), + }, + ), + event( + root, + 9, + EventKind::NewInnerTournament { + match_id_hash: id_hash, + child: inner, + }, + ), + event(inner, 10, join(30)), + event(inner, 11, join(40)), + event( + inner, + 12, + EventKind::MatchCreated { + one: digest(30), + two: digest(40), + left_of_two: digest(41), + }, + ), + ]; + for e in &script { + fold.apply(e).unwrap(); + } + + assert_eq!(fold.addresses(), vec![root, inner]); + + let t = fold.tournament(&root).unwrap(); + assert_eq!(t.level, 0); + assert!(t.parent.is_none()); + assert_eq!(t.commitments.len(), 2); + assert_eq!( + t.commitments[&digest(10)].final_state, + digest(110), + "final state rides the join event" + ); + let m = t.match_by_id_hash(&id_hash).unwrap(); + assert_eq!(m.advances, 1); + assert_eq!(m.inner_tournament, Some(inner)); + assert!(m.is_live()); + + let t = fold.tournament(&inner).unwrap(); + assert_eq!(t.level, 1); + assert_eq!(t.parent, Some((root, id_hash))); + assert_eq!(t.matches.len(), 1); + assert_eq!(t.commitments[&digest(30)].latest_match, Some(0)); + + // The inner resolves; the parent match is closed by the child. + let inner_id = MatchID { + commitment_one: digest(30), + commitment_two: digest(40), + }; + let mut fold2 = fold.clone(); + fold2 + .apply(&event( + inner, + 20, + EventKind::MatchDeleted { + match_id_hash: inner_id.hash(), + reason: MatchDeletionReason::Step, + winner: WinnerCommitment::One, + }, + )) + .unwrap(); + fold2 + .apply(&event( + root, + 21, + EventKind::MatchDeleted { + match_id_hash: id_hash, + reason: MatchDeletionReason::ChildTournament, + winner: WinnerCommitment::One, + }, + )) + .unwrap(); + let m = fold2.tournament(&root).unwrap().matches[0].clone(); + assert_eq!( + m.deleted, + Some((MatchDeletionReason::ChildTournament, WinnerCommitment::One)) + ); + assert_eq!(fold2.tournament(&root).unwrap().live_matches().count(), 0); + } + + /// The fold refuses malformed streams instead of folding garbage. + #[test] + fn fold_refuses_malformed_streams() { + let root = address(1); + let mut fold = Fold::new(root); + + // unknown tournament + assert!(fold.apply(&event(address(9), 1, join(10))).is_err()); + + // advance before creation + assert!( + fold.apply(&event( + root, + 1, + EventKind::MatchAdvanced { + match_id_hash: digest(1), + other_parent: digest(2), + left_node: digest(3), + }, + )) + .is_err() + ); + + // match between unjoined commitments + assert!( + fold.apply(&event( + root, + 1, + EventKind::MatchCreated { + one: digest(10), + two: digest(20), + left_of_two: digest(21), + }, + )) + .is_err() + ); + + // double join + fold.apply(&event(root, 1, join(10))).unwrap(); + assert!(fold.apply(&event(root, 2, join(10))).is_err()); + } +} diff --git a/prt/client-rs/core/src/tournament/mod.rs b/cartesi-rollups/node/src/tournament/mod.rs similarity index 80% rename from prt/client-rs/core/src/tournament/mod.rs rename to cartesi-rollups/node/src/tournament/mod.rs index ad83f5f8f..0292d999e 100644 --- a/prt/client-rs/core/src/tournament/mod.rs +++ b/cartesi-rollups/node/src/tournament/mod.rs @@ -2,14 +2,13 @@ //! of tournaments; and the struct [EthArenaSender] that is responsible for the sending transactions //! to tournaments -mod tournament; -pub use tournament::*; - -mod config; -pub use config::*; +mod types; +pub use types::*; mod reader; pub use reader::*; mod sender; pub use sender::*; + +pub mod fold; diff --git a/cartesi-rollups/node/src/tournament/reader.rs b/cartesi-rollups/node/src/tournament/reader.rs new file mode 100644 index 000000000..1bb02bc61 --- /dev/null +++ b/cartesi-rollups/node/src/tournament/reader.rs @@ -0,0 +1,598 @@ +//! The tournament reader: structure from the event fold, volatile +//! state from per-tick point reads (docs/plans/node-refactor.md, +//! workstream 5, phase 2). +//! +//! Every tick folds from genesis, but only the tail is fetched live: +//! events at or below the chain's finalized block are persisted into +//! storage (the dispute role's tournament_events log) as they +//! finalize, and each tick replays the stored prefix, fetches +//! watermark+1..latest for every discovered tournament, and persists +//! the newly finalized slice. The fold itself is unchanged from +//! phase 1 - still pure, still fed every event in order, and cold +//! start still equals tick (a respawned node replays its stored +//! prefix instead of refetching hundreds of blocks of logs). +//! +//! Reorg stance, unchanged: persisted events are finalized by +//! definition; the tail past the watermark is scratch, refetched +//! every tick, and acting on tail-derived state is safe because the +//! arena sender is revert-tolerant. +//! +//! The overlay reads what events cannot determine (see the fold +//! module doc): live match positions (getMatch, getMatchCycle), +//! clocks (getCommitment), winners (arbitrationResult, +//! innerTournamentWinner), elimination readiness, and the level +//! constants. The whole tick observes ONE block: events are fetched +//! to the tick's head and every point read is pinned at that same +//! height. Unpinned reads raced the advancing chain - a clock could +//! start ticking after the head was sampled and carry a start +//! instant beyond the tick's block stamp (crashed a kill_mid_match +//! run, 2026-07-09). The tail between ticks is scratch, re-derived +//! next tick, and acting on tip-derived state is safe because the +//! arena sender is revert-tolerant. +//! +//! Pinning's residual trade-off: the provider must serve state at a +//! block a few seconds old. Gateways prune (full nodes typically +//! keep 128 blocks; anvil under aggressive fast-forward sometimes +//! less), so a pinned read can transiently miss - the epoch manager +//! retries the whole tick on error rather than dying. A provider +//! that never serves non-latest state would starve the tick +//! entirely; revisit the pin if a real gateway shows that. + +use anyhow::{Result, ensure}; +use std::collections::HashMap; + +use alloy::{ + primitives::{Address, U256}, + rpc::types::Log, +}; + +use crate::chain::Chain; +use crate::storage::Storage; +use crate::tournament::{ + ClockState, DisputeState, MatchLive, TournamentOverlay, TournamentWinner, + fold::{Fold, decode_event}, +}; +use cartesi_prt_contracts::tournament; + +pub struct StateReader { + chain: Chain, + block_created_number: u64, + storage: Storage, +} + +impl StateReader { + pub fn new(chain: Chain, block_created_number: u64, storage: Storage) -> Result { + Ok(Self { + chain, + block_created_number, + storage, + }) + } + + pub async fn fetch_from_root( + &mut self, + root_tournament_address: Address, + ) -> Result { + let latest_block = self.chain.latest_block_number().await?; + // Clamped to the tick's head: the tail fetch stops at latest, + // so nothing past it may be declared persisted. + let finalized_block = self.chain.finalized_block_number().await?.min(latest_block); + + let fold = self + .fold_dispute(root_tournament_address, finalized_block, latest_block) + .await?; + let overlay = self.overlay(&fold, latest_block).await?; + Ok(DisputeState { fold, overlay }) + } + + /// Replays the persisted prefix, fetches every discovered + /// tournament's live tail, and persists the newly finalized + /// slice. Discovery grows the fetch set (an inner tournament's + /// stream only matters once its creation event names it), so the + /// loop runs until no new address appears - bounded by the level + /// count. Coverage induction: a tournament discovered in the tail + /// has its whole stream inside the tail range (its creation event + /// is there), so stored events always cover every discovered + /// stream up to the watermark. + async fn fold_dispute( + &mut self, + root: Address, + finalized_block: u64, + latest_block: u64, + ) -> Result { + let mut fold = Fold::new(root); + + // The persisted prefix, all tournaments in chain order; the + // fold discovers inner tournaments as their creations replay. + for log in &self.storage.tournament_events(root)? { + if let Some(event) = decode_event(log)? { + fold.apply(&event)?; + } + } + let watermark = self.storage.tournament_events_watermark(root)?; + let tail_from = match watermark { + Some(w) => w + 1, + None => self.block_created_number, + }; + + let mut fetched = std::collections::HashSet::new(); + let mut harvest: Vec = Vec::new(); + loop { + let pending: Vec
= fold + .addresses() + .into_iter() + .filter(|address| !fetched.contains(address)) + .collect(); + if pending.is_empty() { + break; + } + + for address in pending { + if latest_block >= tail_from { + let logs = self + .chain + .raw_logs(address, tail_from, latest_block) + .await?; + + for log in &logs { + if let Some(event) = decode_event(log)? { + fold.apply(&event)?; + if event.block <= finalized_block { + harvest.push(log.clone()); + } + } + } + } + fetched.insert(address); + } + } + + // Persist the finalized harvest; the watermark advances even + // when the harvest is empty, keeping the tail bounded. A + // crash before this line re-fetches the same range next tick + // and the append absorbs the replay. + if watermark.is_none_or(|w| finalized_block > w) { + let refs: Vec<&Log> = harvest.iter().collect(); + self.storage + .append_tournament_events(root, finalized_block, &refs)?; + } + + Ok(fold) + } + + /// The point-read overlay over the fold's structure: what the + /// chain owns and events cannot determine (see the fold module + /// doc). Covers reachable tournaments only - the root plus inners + /// whose parent match is still live (a settled inner disappears + /// with its match, exactly as the old recursive walk never + /// reached it). + async fn overlay( + &mut self, + fold: &Fold, + latest_block: u64, + ) -> Result> { + let mut overlay: HashMap = HashMap::new(); + + for tf in fold.tournaments() { + // Discovery order guarantees the parent's overlay is + // already in when its children come up. + let Some(base_cycle) = reachable_base_cycle(tf, &overlay) else { + continue; + }; + + let contract = tournament::Tournament::new(tf.address, self.chain.provider()); + let at = alloy::eips::BlockId::from(latest_block); + + let level_constants = contract.tournamentLevelConstants().block(at).call().await?; + ensure!( + level_constants._level == tf.level, + "chain and fold disagree on tournament level: {} vs {}", + level_constants._level, + tf.level + ); + + let can_be_eliminated = if tf.level > 0 { + contract.canBeEliminated().block(at).call().await? + } else { + false + }; + + // Live matches only, in creation order; the fold knows + // which without a per-match existence probe. + let mut live_matches = HashMap::new(); + for m in tf.live_matches() { + let id_hash = m.id.hash(); + let chain_match = contract.getMatch(id_hash.into()).block(at).call().await?; + ensure!( + chain_match.isInit, + "fold sees live match {id_hash} but the chain does not" + ); + let leaf_cycle = contract + .getMatchCycle(id_hash.into()) + .block(at) + .call() + .await?; + + live_matches.insert( + id_hash, + MatchLive { + other_parent: chain_match.otherParent.into(), + left_node: chain_match.leftNode.into(), + right_node: chain_match.rightNode.into(), + running_leaf_position: chain_match.runningLeafPosition, + current_height: chain_match.currentHeight, + leaf_cycle, + }, + ); + } + + let mut clocks = HashMap::new(); + for c in tf.commitments.values() { + let commitment_return = contract + .getCommitment(c.root.into()) + .block(at) + .call() + .await?; + ensure!( + crate::merkle::Digest::from(commitment_return._1) == c.final_state, + "chain and fold disagree on commitment {}'s final state", + c.root + ); + + clocks.insert( + c.root, + ClockState { + allowance: commitment_return._0.allowance, + start_instant: commitment_return._0.startInstant, + block_number: latest_block, + }, + ); + } + + let winner = match tf.parent { + Some(_) => self.tournament_winner(tf.address, at).await?, + None => self.root_tournament_winner(tf.address, at).await?, + }; + + overlay.insert( + tf.address, + TournamentOverlay { + max_level: level_constants._maxLevel, + log2_stride: level_constants._log2step, + log2_stride_count: level_constants._height, + base_cycle, + winner, + can_be_eliminated, + clocks, + live_matches, + }, + ); + } + + Ok(overlay) + } + + async fn root_tournament_winner( + &mut self, + root_tournament_address: Address, + at: alloy::eips::BlockId, + ) -> Result> { + let root_tournament = + tournament::Tournament::new(root_tournament_address, self.chain.provider()); + let arbitration_result_return = + root_tournament.arbitrationResult().block(at).call().await?; + let (finished, commitment, state) = ( + arbitration_result_return._0, + arbitration_result_return._1, + arbitration_result_return._2, + ); + + if finished { + Ok(Some(TournamentWinner::Root( + commitment.into(), + state.into(), + ))) + } else { + Ok(None) + } + } + + async fn tournament_winner( + &mut self, + tournament_address: Address, + at: alloy::eips::BlockId, + ) -> Result> { + let tournament = tournament::Tournament::new(tournament_address, self.chain.provider()); + let inner_tournament_winner_return = + tournament.innerTournamentWinner().block(at).call().await?; + let (finished, parent_commitment, dangling_commitment) = ( + inner_tournament_winner_return._0, + inner_tournament_winner_return._1, + inner_tournament_winner_return._2, + ); + + if finished { + Ok(Some(TournamentWinner::Inner( + parent_commitment.into(), + dangling_commitment.into(), + ))) + } else { + Ok(None) + } + } +} + +/// The overlay's reachability gate, pure: a tournament is reachable +/// iff it is the root (base cycle zero) or its parent is overlaid +/// with the sealing match still live, in which case the inner level +/// arbitrates that match's leaf cycle. A settled parent match makes +/// the inner history, exactly as the pre-fold recursive walk never +/// descended into it. +fn reachable_base_cycle( + tf: &crate::tournament::fold::TournamentFold, + overlay: &HashMap, +) -> Option { + match tf.parent { + None => Some(U256::ZERO), + Some((parent_address, match_id_hash)) => { + let parent_overlay = overlay.get(&parent_address)?; + let parent_match = parent_overlay.live_matches.get(&match_id_hash)?; + Some(parent_match.leaf_cycle) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::merkle::Digest; + use crate::tournament::fold::{EventKind, TournamentEvent}; + use crate::tournament::{MatchID, MatchLive}; + + fn digest(byte: u8) -> Digest { + Digest::from_digest(&[byte; 32]).unwrap() + } + + fn address(byte: u8) -> Address { + Address::from([byte; 20]) + } + + fn apply(fold: &mut Fold, tournament: Address, kind: EventKind) { + fold.apply(&TournamentEvent { + tournament, + block: 1, + kind, + }) + .unwrap(); + } + + /// Joins two commitments, matches them, seals the match into an + /// inner tournament; returns the sealing match's id hash. + fn seal_inner(fold: &mut Fold, at: Address, child: Address, seed: u8) -> Digest { + let (one, two) = (digest(seed), digest(seed + 1)); + apply( + fold, + at, + EventKind::CommitmentJoined { + root: one, + final_state: digest(seed + 100), + }, + ); + apply( + fold, + at, + EventKind::CommitmentJoined { + root: two, + final_state: digest(seed + 101), + }, + ); + apply( + fold, + at, + EventKind::MatchCreated { + one, + two, + left_of_two: digest(seed + 102), + }, + ); + let id_hash = MatchID { + commitment_one: one, + commitment_two: two, + } + .hash(); + apply( + fold, + at, + EventKind::NewInnerTournament { + match_id_hash: id_hash, + child, + }, + ); + id_hash + } + + fn overlay_with_live(live: &[(Digest, U256)]) -> TournamentOverlay { + TournamentOverlay { + max_level: 3, + log2_stride: 44, + log2_stride_count: 48, + base_cycle: U256::ZERO, + winner: None, + can_be_eliminated: false, + clocks: HashMap::new(), + live_matches: live + .iter() + .map(|(id_hash, leaf_cycle)| { + ( + *id_hash, + MatchLive { + other_parent: digest(0), + left_node: digest(0), + right_node: digest(0), + running_leaf_position: U256::ZERO, + current_height: 0, + leaf_cycle: *leaf_cycle, + }, + ) + }) + .collect(), + } + } + + #[test] + fn root_is_always_reachable_at_cycle_zero() { + let fold = Fold::new(address(1)); + let overlay = HashMap::new(); + let root = fold.tournament(&address(1)).unwrap(); + assert_eq!(reachable_base_cycle(root, &overlay), Some(U256::ZERO)); + } + + #[test] + fn inner_reads_its_base_cycle_off_the_parents_live_match() { + let (root, inner) = (address(1), address(2)); + let mut fold = Fold::new(root); + let id_hash = seal_inner(&mut fold, root, inner, 10); + + let mut overlay = HashMap::new(); + overlay.insert(root, overlay_with_live(&[(id_hash, U256::from(0x4400))])); + + let tf = fold.tournament(&inner).unwrap(); + assert_eq!(reachable_base_cycle(tf, &overlay), Some(U256::from(0x4400))); + } + + #[test] + fn inner_of_a_settled_match_is_history() { + let (root, inner) = (address(1), address(2)); + let mut fold = Fold::new(root); + let _ = seal_inner(&mut fold, root, inner, 10); + + // The parent is overlaid, but the sealing match is no longer + // among its live matches: the inner disappeared with it. + let mut overlay = HashMap::new(); + overlay.insert(root, overlay_with_live(&[])); + + let tf = fold.tournament(&inner).unwrap(); + assert_eq!(reachable_base_cycle(tf, &overlay), None); + } + + #[test] + fn grandchild_of_an_unreachable_parent_stays_unreachable() { + let (root, mid, leaf) = (address(1), address(2), address(3)); + let mut fold = Fold::new(root); + let _ = seal_inner(&mut fold, root, mid, 10); + let leaf_id_hash = seal_inner(&mut fold, mid, leaf, 30); + + // Root settled mid's match, so mid never got an overlay; the + // grandchild must not resurrect through its own (live) match. + let mut overlay = HashMap::new(); + overlay.insert(root, overlay_with_live(&[])); + let _ = leaf_id_hash; + + let tf = fold.tournament(&leaf).unwrap(); + assert_eq!(reachable_base_cycle(tf, &overlay), None); + } +} + +/// Fold phase 2's equivalence oracle, against the chain recordings: +/// persisting a finalized prefix through the real storage path and +/// folding stored-plus-tail must reproduce the all-at-once fold +/// EXACTLY, at every block boundary of the recorded dispute. No +/// split point may change what the Hero sees; this is what makes the +/// persisted log safe to trust across restarts. +#[cfg(test)] +mod phase2_tests { + use super::*; + use crate::storage::Storage; + use alloy::sol_types::SolEvent; + use cartesi_dave_contracts::dave_consensus::DaveConsensus; + + fn recorded_logs(name: &str) -> Vec { + let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/chain-recordings") + .join(name); + let raw: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + raw["logs"] + .as_array() + .expect("recording carries a log array") + .iter() + .map(|log| serde_json::from_value(log.clone()).expect("log decodes")) + .collect() + } + + fn migrated_storage() -> (tempfile::TempDir, Storage) { + let dir = tempfile::tempdir().unwrap(); + let mut conn = rusqlite::Connection::open(dir.path().join("db.sqlite3")).unwrap(); + crate::storage::sql::migrations::migrate_to_latest(&mut conn).unwrap(); + drop(conn); + let storage = Storage::new(dir.path()).unwrap(); + (dir, storage) + } + + #[test] + fn stored_prefix_plus_live_tail_equals_the_whole_stream() { + let logs = recorded_logs("echo_simple.json"); + let root = logs + .iter() + .filter_map(|log| DaveConsensus::EpochSealed::decode_log(&log.inner).ok()) + .find(|e| e.epochNumber == U256::from(1)) + .expect("epoch 1 is the disputed epoch") + .tournament; + + // The whole-stream fold, discovery inline (chain order makes + // one pass sufficient), keeping the applied raw logs. + let mut full_fold = Fold::new(root); + let mut dispute_logs: Vec<&Log> = Vec::new(); + for log in &logs { + let Some(event) = decode_event(log).unwrap() else { + continue; + }; + if full_fold.tournament(&event.tournament).is_none() { + continue; // another epoch's tournament or foreign contract + } + full_fold.apply(&event).unwrap(); + dispute_logs.push(log); + } + assert!(!dispute_logs.is_empty()); + + let mut split_points: Vec = dispute_logs + .iter() + .map(|log| log.block_number.expect("recorded log has a block")) + .collect(); + split_points.dedup(); + + for split in split_points { + let (_dir, mut storage) = migrated_storage(); + + let prefix: Vec<&Log> = dispute_logs + .iter() + .filter(|log| log.block_number.unwrap() <= split) + .copied() + .collect(); + storage + .append_tournament_events(root, split, &prefix) + .unwrap(); + assert_eq!( + storage.tournament_events_watermark(root).unwrap(), + Some(split) + ); + + // The round trip: stored prefix replayed, live tail applied. + let mut fold = Fold::new(root); + for log in &storage.tournament_events(root).unwrap() { + if let Some(event) = decode_event(log).unwrap() { + fold.apply(&event).unwrap(); + } + } + for log in dispute_logs + .iter() + .filter(|log| log.block_number.unwrap() > split) + { + let event = decode_event(log).unwrap().expect("dispute log decodes"); + fold.apply(&event).unwrap(); + } + + assert_eq!(fold, full_fold, "fold diverges when split at block {split}"); + } + } +} diff --git a/prt/client-rs/core/src/tournament/sender.rs b/cartesi-rollups/node/src/tournament/sender.rs similarity index 97% rename from prt/client-rs/core/src/tournament/sender.rs rename to cartesi-rollups/node/src/tournament/sender.rs index 0be6d571b..826c04a6b 100644 --- a/prt/client-rs/core/src/tournament/sender.rs +++ b/cartesi-rollups/node/src/tournament/sender.rs @@ -1,19 +1,22 @@ //! This module defines the struct [EthArenaSender] that is responsible for the sending transactions //! to tournaments -use crate::strategy::error::Result; +use crate::hero::error::Result; use alloy::{ contract::Error, network::Ethereum, + primitives::{Address, B256, Bytes, U256}, providers::{DynProvider, PendingTransactionBuilder}, - sol_types::private::{Address, B256, Bytes}, }; use async_trait::async_trait; use log::{trace, warn}; -use ruint::aliases::U256; -use crate::{machine::MachineProof, tournament::MatchID}; -use cartesi_dave_merkle::{Digest, MerkleProof}; +use crate::tournament::MatchID; + +/// A transition witness in chain encoding (Ruler::prove_transition's +/// output). +pub type MachineProof = Vec; +use crate::merkle::{Digest, MerkleProof}; use cartesi_prt_contracts::tournament; /// Default gas limit for refundable tournament calls (body + refund modifier overhead; diff --git a/cartesi-rollups/node/src/tournament/types.rs b/cartesi-rollups/node/src/tournament/types.rs new file mode 100644 index 000000000..3e7a989c6 --- /dev/null +++ b/cartesi-rollups/node/src/tournament/types.rs @@ -0,0 +1,302 @@ +//! The tournament value types shared by the fold, the reader, and the +//! Hero, and the reader's product: [`DisputeState`], the dispute's +//! event-derived structure plus the per-tick point-read overlay. + +use crate::merkle::Digest; +use alloy::primitives::{Address, U256}; +use std::collections::HashMap; + +use crate::tournament::fold::{Fold, TournamentFold}; + +/// Struct used to identify a match. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MatchID { + pub commitment_one: Digest, + pub commitment_two: Digest, +} + +impl MatchID { + /// Generates a new [Digest] + pub fn hash(&self) -> Digest { + self.commitment_one.join(&self.commitment_two) + } +} + +// TODO: this can be optimized if the bindings generated with only one shared `Id` struct +impl From for cartesi_prt_contracts::tournament::Match::Id { + fn from(match_id: MatchID) -> Self { + cartesi_prt_contracts::tournament::Match::Id { + commitmentOne: match_id.commitment_one.into(), + commitmentTwo: match_id.commitment_two.into(), + } + } +} + +/// Struct used to communicate the state of a clock. +#[derive(Clone, Copy, Debug)] +pub struct ClockState { + pub allowance: u64, + pub start_instant: u64, + pub block_number: u64, +} + +// Clock arithmetic is saturating throughout: these values come off +// the chain, and a display or comparison must degrade on a weird +// read, never crash the node. An unpinned overlay read once handed +// this type a clock started AFTER the tick's block stamp, and the +// display's subtraction underflow killed the epoch-manager thread +// (kill_mid_match, 2026-07-09). The reader now pins every read at +// the tick's block, which makes that state unreachable; saturation +// keeps the type total anyway. +impl ClockState { + pub fn has_time(&self) -> bool { + if self.start_instant == 0 { + true + } else { + self.deadline() > self.block_number + } + } + + pub fn time_since_timeout(&self) -> u64 { + if self.start_instant == 0 { + 0 + } else { + self.block_number.saturating_sub(self.deadline()) + } + } + + // deadline of clock if it's ticking + fn deadline(&self) -> u64 { + self.start_instant.saturating_add(self.allowance) + } +} + +impl std::fmt::Display for ClockState { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.start_instant == 0 { + write!(f, "clock paused, {} blocks left", self.allowance) + } else { + let time_elapsed = self.block_number.saturating_sub(self.start_instant); + if self.allowance >= time_elapsed { + write!( + f, + "clock ticking, {} blocks left", + self.allowance - time_elapsed + ) + } else { + write!( + f, + "clock ticking, {} blocks overdue", + time_elapsed - self.allowance + ) + } + } + } +} + +/// Enum used to represent the winner of a tournament. +#[derive(Clone, PartialEq, Debug)] +pub enum TournamentWinner { + Root(Digest, Digest), + Inner(Digest, Digest), +} + +/// What events cannot determine about a running match (see the fold +/// module doc): the contract's live positioning, point-read each tick. +#[derive(Clone, Copy, Debug)] +pub struct MatchLive { + pub other_parent: Digest, + pub left_node: Digest, + pub right_node: Digest, + pub running_leaf_position: U256, + pub current_height: u64, + pub leaf_cycle: U256, +} + +/// One reachable tournament's point-read overlay: everything the +/// chain owns that the event fold deliberately does not derive. +#[derive(Clone, Debug)] +pub struct TournamentOverlay { + /// Level geometry from tournamentLevelConstants; the level itself + /// is asserted against the fold's at assembly. + pub max_level: u64, + pub log2_stride: u64, + pub log2_stride_count: u64, + /// The leftmost big-cycle this tournament arbitrates: the parent's + /// sealed match leaf cycle, zero at the root. + pub base_cycle: U256, + pub winner: Option, + pub can_be_eliminated: bool, + /// Clock per joined commitment, stamped with the fetch block. + pub clocks: HashMap, + /// Live positioning per live match, keyed by MatchID hash. + pub live_matches: HashMap, +} + +/// The reader's product: structure from the event fold, volatile +/// state from the overlay. The overlay covers REACHABLE tournaments +/// only - the root, plus inners whose parent match is still live; a +/// settled inner disappears with its match, exactly as the old +/// recursive walk never reached it. +#[derive(Clone, Debug)] +pub struct DisputeState { + pub fold: Fold, + pub overlay: HashMap, +} + +impl DisputeState { + /// A reachable tournament's structure and overlay together. + pub fn tournament(&self, address: &Address) -> Option<(&TournamentFold, &TournamentOverlay)> { + let overlay = self.overlay.get(address)?; + let fold = self + .fold + .tournament(address) + .expect("overlay covers only folded tournaments"); + Some((fold, overlay)) + } + + /// Every reachable tournament, parents before children. + pub fn reachable(&self) -> impl Iterator { + self.fold + .tournaments() + .filter_map(|tf| self.overlay.get(&tf.address).map(|ov| (tf, ov))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tournament::fold::{EventKind, TournamentEvent}; + + fn digest(byte: u8) -> Digest { + Digest::from_digest(&[byte; 32]).unwrap() + } + + fn address(byte: u8) -> Address { + Address::from([byte; 20]) + } + + fn bare_overlay() -> TournamentOverlay { + TournamentOverlay { + max_level: 3, + log2_stride: 44, + log2_stride_count: 48, + base_cycle: U256::ZERO, + winner: None, + can_be_eliminated: false, + clocks: HashMap::new(), + live_matches: HashMap::new(), + } + } + + /// Reachability IS overlay membership: the iterator walks + /// discovery order and silently skips tournaments the overlay + /// never covered, and point lookups on them answer None - the + /// contract the GC's sweep and the Hero's descent both lean on. + #[test] + fn dispute_state_serves_only_the_overlaid() { + let (root, inner) = (address(1), address(2)); + let mut fold = Fold::new(root); + for (seed, kind) in [ + ( + 10u8, + EventKind::CommitmentJoined { + root: digest(10), + final_state: digest(110), + }, + ), + ( + 20, + EventKind::CommitmentJoined { + root: digest(20), + final_state: digest(120), + }, + ), + ] { + let _ = seed; + fold.apply(&TournamentEvent { + tournament: root, + block: 1, + kind, + }) + .unwrap(); + } + fold.apply(&TournamentEvent { + tournament: root, + block: 2, + kind: EventKind::MatchCreated { + one: digest(10), + two: digest(20), + left_of_two: digest(21), + }, + }) + .unwrap(); + let id_hash = MatchID { + commitment_one: digest(10), + commitment_two: digest(20), + } + .hash(); + fold.apply(&TournamentEvent { + tournament: root, + block: 3, + kind: EventKind::NewInnerTournament { + match_id_hash: id_hash, + child: inner, + }, + }) + .unwrap(); + + // Overlay covers the root only: the inner is history. + let mut overlay = HashMap::new(); + overlay.insert(root, bare_overlay()); + let dispute = DisputeState { fold, overlay }; + + let reachable: Vec
= dispute.reachable().map(|(tf, _)| tf.address).collect(); + assert_eq!(reachable, vec![root]); + assert!(dispute.tournament(&root).is_some()); + assert!(dispute.tournament(&inner).is_none()); + } + + /// The 2026-07-09 kill_mid_match crash: a clock read fresher than + /// the tick's block stamp (start_instant beyond block_number) must + /// display and answer queries, not underflow. + #[test] + fn clock_from_the_future_degrades_instead_of_crashing() { + let clock = ClockState { + allowance: 300, + start_instant: 1000, + block_number: 998, + }; + assert_eq!(format!("{clock}"), "clock ticking, 300 blocks left"); + assert!(clock.has_time()); + assert_eq!(clock.time_since_timeout(), 0); + } + + #[test] + fn clock_states_display_their_phase() { + let paused = ClockState { + allowance: 300, + start_instant: 0, + block_number: 50, + }; + assert_eq!(format!("{paused}"), "clock paused, 300 blocks left"); + + let ticking = ClockState { + allowance: 300, + start_instant: 100, + block_number: 150, + }; + assert_eq!(format!("{ticking}"), "clock ticking, 250 blocks left"); + assert!(ticking.has_time()); + assert_eq!(ticking.time_since_timeout(), 0); + + let overdue = ClockState { + allowance: 300, + start_instant: 100, + block_number: 500, + }; + assert_eq!(format!("{overdue}"), "clock ticking, 100 blocks overdue"); + assert!(!overdue.has_time()); + assert_eq!(overdue.time_since_timeout(), 100); + } +} diff --git a/cartesi-rollups/node/state-manager/Cargo.toml b/cartesi-rollups/node/state-manager/Cargo.toml deleted file mode 100644 index ce6ac36a3..000000000 --- a/cartesi-rollups/node/state-manager/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "rollups-state-manager" -version = { workspace = true } - -authors = { workspace = true } -description = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -readme = { workspace = true } -repository = { workspace = true } - -[dependencies] -cartesi-dave-merkle = { workspace = true } -cartesi-machine = { workspace = true } -cartesi-prt-core = { workspace = true } - -alloy = { workspace = true } - -lazy_static = { workspace = true } -rusqlite = { workspace = true } -rusqlite_migration = { workspace = true } - -hex = { workspace = true } -tempfile = "3" - -anyhow = { workspace = true } -thiserror = { workspace = true } diff --git a/cartesi-rollups/node/state-manager/src/lib.rs b/cartesi-rollups/node/state-manager/src/lib.rs deleted file mode 100644 index bb83317d2..000000000 --- a/cartesi-rollups/node/state-manager/src/lib.rs +++ /dev/null @@ -1,128 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pub mod persistent_state_access; -pub mod rollups_machine; -pub mod state_manager; -pub mod sync; - -use alloy::primitives::Address; -pub use state_manager::StateAccessError; -pub use state_manager::StateManager; - -pub(crate) mod sql; - -use cartesi_dave_merkle::Digest; -use cartesi_machine::types::Hash; - -pub type Blob = Vec; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CommitmentLeaf { - pub hash: Hash, - pub repetitions: u64, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Proof(Vec<[u8; 32]>); - -impl Proof { - pub fn new(siblings: Vec<[u8; 32]>) -> Self { - Self(siblings) - } - - pub fn inner(&self) -> Vec<[u8; 32]> { - self.0.clone() - } - - fn from_flattened(input: Vec) -> Self { - // Ensure the length is a multiple of 32 - assert!( - input.len() % 32 == 0, - "Input length must be a multiple of 32" - ); - - let mut result = Vec::new(); - - for chunk in input.chunks(32) { - let mut array = [0u8; 32]; - array.copy_from_slice(chunk); - result.push(array); - } - - Proof(result) - } - - fn flatten(&self) -> Vec { - self.0 - .iter() - .flat_map(|array| array.iter()) - .copied() - .collect() - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Settlement { - pub computation_hash: Digest, - pub final_state: Hash, - pub output_merkle: Hash, - pub output_proof: Proof, -} - -#[derive(Clone, Debug, Default)] -pub struct InputId { - pub epoch_number: u64, - pub input_index_in_epoch: u64, -} - -impl InputId { - pub fn increment_index(self) -> Self { - Self { - epoch_number: self.epoch_number, - input_index_in_epoch: self.input_index_in_epoch + 1, - } - } - - pub fn increment_epoch(self) -> Self { - Self { - epoch_number: self.epoch_number + 1, - input_index_in_epoch: 0, - } - } - - pub fn validate_next(&self, next: &Self) -> bool { - match self { - InputId { - epoch_number, - input_index_in_epoch, - } if next.epoch_number == *epoch_number - && next.input_index_in_epoch == input_index_in_epoch + 1 => - { - true - } - - InputId { epoch_number, .. } - if next.epoch_number > *epoch_number && next.input_index_in_epoch == 0 => - { - true - } - - _ => false, - } - } -} - -#[derive(Clone, Debug)] -pub struct Input { - pub id: InputId, - pub data: Blob, -} - -#[derive(Clone, Debug)] -pub struct Epoch { - pub epoch_number: u64, - pub input_index_boundary: u64, - pub root_tournament: Address, - pub block_created_number: u64, -} diff --git a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs deleted file mode 100644 index f49a85c76..000000000 --- a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs +++ /dev/null @@ -1,547 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use std::path::{Path, PathBuf}; - -use crate::{ - CommitmentLeaf, Epoch, Input, InputId, Settlement, StateManager, - rollups_machine::{self, RollupsMachine}, - sql::*, - state_manager::Result, -}; - -use alloy::primitives::U256; -use cartesi_dave_merkle::{Digest, MerkleBuilder}; -use cartesi_machine::types::Hash; -use rusqlite::Connection; - -#[derive(Debug)] -pub struct PersistentStateAccess { - connection: Connection, - state_dir: PathBuf, -} - -impl PersistentStateAccess { - pub fn migrate( - state_dir: &Path, - initial_machine_path: &Path, - genesis_block_number: u64, - ) -> Result { - create_empty_state_dir_if_needed(state_dir)?; - let state_dir = state_dir.canonicalize().map_err(anyhow::Error::from)?; - let connection = migrate(&state_dir, initial_machine_path, genesis_block_number)?; - - Ok(Self { - connection, - state_dir, - }) - } - - pub fn new(state_dir: &Path) -> Result { - let state_dir = state_dir.canonicalize().map_err(anyhow::Error::from)?; - let connection = create_connection(&state_dir)?; - - Ok(Self { - connection, - state_dir, - }) - } - - pub fn db_path(&self) -> PathBuf { - db_path(&self.state_dir) - } - - pub fn state_dir(&self) -> &Path { - &self.state_dir - } -} - -impl StateManager for PersistentStateAccess { - // - // Consensus Data - // - - fn epoch(&mut self, epoch_number: u64) -> Result> { - consensus_data::epoch(&self.connection, epoch_number) - } - - fn epoch_count(&mut self) -> Result { - consensus_data::epoch_count(&self.connection) - } - - fn last_sealed_epoch(&mut self) -> Result> { - consensus_data::last_sealed_epoch(&self.connection) - } - - fn input(&mut self, id: &InputId) -> Result> { - consensus_data::input(&self.connection, id) - } - - fn inputs(&mut self, epoch_number: u64) -> Result>> { - consensus_data::inputs(&self.connection, epoch_number) - } - - fn input_count(&mut self, epoch_number: u64) -> Result { - consensus_data::input_count(&self.connection, epoch_number) - } - - fn last_input(&mut self) -> Result> { - consensus_data::last_input(&self.connection) - } - - fn insert_consensus_data<'a>( - &mut self, - last_processed_block: u64, - inputs: impl Iterator, - epochs: impl Iterator, - ) -> Result<()> { - let tx = self.connection.transaction().map_err(anyhow::Error::from)?; - consensus_data::update_last_processed_block(&tx, last_processed_block)?; - consensus_data::insert_inputs(&tx, inputs)?; - consensus_data::insert_epochs(&tx, epochs)?; - tx.commit().map_err(anyhow::Error::from)?; - - Ok(()) - } - - fn latest_processed_block(&mut self) -> Result { - consensus_data::last_processed_block(&self.connection) - } - - // - // Rollup Data - // - fn advance_accepted( - &mut self, - machine: &mut RollupsMachine, - leafs: &[CommitmentLeaf], - ) -> Result<()> { - assert!(!leafs.is_empty()); - let epoch = machine.epoch(); - let next_input_index = machine.next_input_index_in_epoch(); - let processed_input_index = next_input_index - 1; - - rollup_data::insert_state_hashes_for_input( - &self.connection, - epoch, - processed_input_index, - leafs, - )?; - - let (dest_dir, state_hash) = { - let snapshots_path = snapshots_path(&self.state_dir); - machine - .store_if_needed(&snapshots_path) - .map_err(anyhow::Error::from)? - }; - - rollup_data::insert_snapshot( - &self.connection, - epoch, - next_input_index, - &state_hash, - &dest_dir, - )?; - rollup_data::gc_previous_advances(&self.connection, epoch, next_input_index)?; - - Ok(()) - } - - fn advance_reverted( - &mut self, - machine: &mut RollupsMachine, - leafs: &[CommitmentLeaf], - ) -> Result<()> { - assert!(!leafs.is_empty()); - let epoch = machine.epoch(); - let next_input_index = machine.next_input_index_in_epoch(); - let processed_input_index = next_input_index - 1; - - rollup_data::insert_state_hashes_for_input( - &self.connection, - epoch, - processed_input_index, - leafs, - )?; - - let (snapshot_path, snapshot_epoch, snapshot_input) = - rollup_data::latest_snapshot_path(&self.connection)?; - - assert_eq!(snapshot_epoch, epoch); - assert_eq!(snapshot_input, processed_input_index); - - // load rollups machine from previous successful (ACCEPT) snapshot - let mut reverted_machine = RollupsMachine::new(&snapshot_path, epoch, next_input_index)?; - - rollup_data::insert_snapshot( - &self.connection, - epoch, - next_input_index, - &reverted_machine.state_hash()?, - &snapshot_path, - )?; - rollup_data::gc_previous_advances(&self.connection, epoch, next_input_index)?; - - // Update the passed machine to match the reverted state - *machine = reverted_machine; - Ok(()) - } - - fn next_input_id(&mut self) -> Result { - rollup_data::next_input_to_be_processed(&self.connection) - } - - fn epoch_state_hashes(&mut self, epoch_number: u64) -> Result> { - let mut leafs = rollup_data::get_all_commitments(&self.connection, epoch_number)?; - - let total_reps = leafs.iter().fold(0, |acc, leaf| acc + leaf.repetitions); - if let Some(last) = leafs.last_mut() { - last.repetitions += rollups_machine::STRIDE_COUNT_IN_EPOCH - total_reps - } - - Ok(leafs) - } - - fn settlement_info(&mut self, epoch_number: u64) -> Result> { - rollup_data::settlement_info(&self.connection, epoch_number) - } - - fn roll_epoch(&mut self) -> Result<()> { - let mut machine = self.latest_snapshot()?; - let previous_epoch_number = machine.epoch(); - - let settlement = { - let leafs = rollup_data::get_all_commitments(&self.connection, previous_epoch_number)?; - - let (computation_hash, final_state) = if !leafs.is_empty() { - build_commitment_from_hashes(&leafs) - } else { - assert_eq!(machine.next_input_index_in_epoch(), 0); - build_commitment_from_hashes(&[CommitmentLeaf { - hash: machine.state_hash()?, - repetitions: 1, - }]) - }; - - let (output_merkle, output_proof) = machine.outputs_proof()?; - - Settlement { - computation_hash, - final_state, - output_merkle, - output_proof, - } - }; - - machine.finish_epoch(); - - let new_epoch_number = machine.epoch(); - create_epoch_dir(&self.state_dir, new_epoch_number)?; - - let (dest_dir, state_hash) = { - let snapshots_path = snapshots_path(&self.state_dir); - machine - .store_if_needed(&snapshots_path) - .map_err(anyhow::Error::from)? - }; - - let tx = self.connection.transaction().map_err(anyhow::Error::from)?; - rollup_data::insert_snapshot(&tx, new_epoch_number, 0, &state_hash, &dest_dir)?; - rollup_data::insert_settlement_info(&tx, &settlement, previous_epoch_number)?; - tx.commit().map_err(anyhow::Error::from)?; - - if previous_epoch_number >= 1 { - rollup_data::gc_old_epochs(&self.connection, previous_epoch_number - 1)?; - } - - Ok(()) - } - - fn snapshot(&mut self, epoch_number: u64, input_number: u64) -> Result> { - let ret = if let Some(path) = - rollup_data::snapshot_path_for_epoch(&self.connection, epoch_number, input_number)? - { - Some(RollupsMachine::new(&path, epoch_number, 0)?) - } else { - None - }; - - Ok(ret) - } - - fn latest_snapshot(&mut self) -> Result { - let (path, epoch_number, input_number) = - rollup_data::latest_snapshot_path(&self.connection)?; - Ok(RollupsMachine::new(&path, epoch_number, input_number)?) - } - - fn snapshot_dir(&mut self, epoch_number: u64, input_number: u64) -> Result> { - rollup_data::snapshot_path_for_epoch(&self.connection, epoch_number, input_number) - } - - // - // Directory - // - - fn epoch_directory(&mut self, epoch_number: u64) -> Result { - create_epoch_dir(&self.state_dir, epoch_number) - } -} - -fn build_commitment_from_hashes(state_hashes: &[CommitmentLeaf]) -> (Digest, Hash) { - let mut builder = MerkleBuilder::default(); - - assert!(!state_hashes.is_empty()); - let (last, hashes) = state_hashes.split_last().unwrap(); - - for state_hash in hashes { - builder.append_repeated(Digest::new(state_hash.hash), state_hash.repetitions); - } - - // If count is zero, this means tree is full, but we still have the last leaf to add. - assert_ne!(builder.count(), Some(U256::ZERO)); - - // Complete tree - builder.append_repeated( - Digest::new(last.hash), - U256::from(rollups_machine::STRIDE_COUNT_IN_EPOCH) - builder.count().unwrap_or(U256::ZERO), - ); - - let tree = builder.build(); - (tree.root_hash(), last.hash) -} - -#[cfg(test)] -mod tests { - use alloy::primitives::Address; - use cartesi_machine::{ - Machine, - config::{ - machine::{MachineConfig, RAMConfig}, - runtime::RuntimeConfig, - }, - }; - - use super::*; - - fn setup() -> (tempfile::TempDir, PersistentStateAccess) { - let state_dir_ = tempfile::tempdir().unwrap(); - let state_dir = state_dir_.path(); - - let machine_path = state_dir.join("_my_machine_image"); - let mut machine = Machine::create( - &MachineConfig::new_with_ram(RAMConfig { - length: 134217728, - backing_store: cartesi_machine::config::machine::BackingStoreConfig { - data_filename: "../../../test/programs/linux.bin".into(), - ..Default::default() - }, - }), - &RuntimeConfig::default(), - ) - .unwrap(); - machine.store(&machine_path).unwrap(); - - let acc = PersistentStateAccess::migrate(state_dir, &machine_path, 0).unwrap(); - - (state_dir_, acc) - } - - #[test] - fn test_state_access() -> super::Result<()> { - let input_0_bytes = b"hello"; - let input_1_bytes = b"world"; - - let (_handle, mut access) = setup(); - - let mut initial_snapshot = access.latest_snapshot().unwrap(); - assert_eq!(initial_snapshot.epoch(), 0); - - access.insert_consensus_data( - 20, - [ - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 0, - }, - data: input_0_bytes.to_vec(), - }, - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 1, - }, - data: input_1_bytes.to_vec(), - }, - ] - .into_iter(), - [&Epoch { - epoch_number: 0, - input_index_boundary: 12, - root_tournament: Address::ZERO, - block_created_number: 0, - }] - .into_iter(), - )?; - - assert_eq!( - access - .input(&InputId { - epoch_number: 0, - input_index_in_epoch: 0 - })? - .map(|x| x.data), - Some(input_0_bytes.to_vec()), - "input 0 bytes should match" - ); - assert_eq!( - access - .input(&InputId { - epoch_number: 0, - input_index_in_epoch: 1 - })? - .map(|x| x.data), - Some(input_1_bytes.to_vec()), - "input 1 bytes should match" - ); - assert!( - access - .input(&InputId { - epoch_number: 0, - input_index_in_epoch: 2 - })? - .is_none(), - "input 2 shouldn't exist" - ); - - assert!( - access - .insert_consensus_data( - 21, - [&Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 1, - }, - data: input_0_bytes.to_vec(), - }] - .into_iter(), - [].into_iter(), - ) - .is_err(), - "duplicate input index should fail" - ); - assert!( - access - .insert_consensus_data( - 21, - [&Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 3, - }, - data: input_0_bytes.to_vec(), - }] - .into_iter(), - [].into_iter(), - ) - .is_err(), - "input index should be sequential" - ); - assert!( - access - .insert_consensus_data( - 21, - [&Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 2, - }, - data: input_1_bytes.to_vec(), - }] - .into_iter(), - [].into_iter(), - ) - .is_ok(), - "add sequential input should succeed" - ); - - assert_eq!( - access.latest_processed_block()?, - 21, - "latest block should match" - ); - - assert!( - access.epoch_state_hashes(0)?.is_empty(), - "machine state hashes shouldn't exist" - ); - - let commitment_leaf_1 = CommitmentLeaf { - hash: [1; 32], - repetitions: 1, - }; - let commitment_leaf_2 = CommitmentLeaf { - hash: [2; 32], - repetitions: 5, - }; - - initial_snapshot.increment_input(); - access.advance_accepted( - &mut initial_snapshot, - std::slice::from_ref(&commitment_leaf_1), - )?; - - assert_eq!( - access.epoch_state_hashes(0)?[0], - CommitmentLeaf { - hash: [1; 32], - repetitions: rollups_machine::STRIDE_COUNT_IN_EPOCH, - }, - "machine state 1 data should match" - ); - assert_eq!( - access.epoch_state_hashes(0)?.len(), - 1, - "machine state 1 count shouldn't exist" - ); - - initial_snapshot.increment_input(); - access.advance_reverted( - &mut initial_snapshot, - std::slice::from_ref(&commitment_leaf_2), - )?; - - assert_eq!( - access.epoch_state_hashes(0)?.len(), - 2, - "machine state 2 count shouldn't exist" - ); - - assert!( - access.settlement_info(1)?.is_none(), - "computation_hash shouldn't exist" - ); - - let (output_merkle, output_proof) = initial_snapshot.outputs_proof()?; - access.roll_epoch()?; - assert_eq!(access.latest_snapshot()?.epoch(), 1); - - let (computation_hash, final_state) = - build_commitment_from_hashes(&[commitment_leaf_1.clone(), commitment_leaf_2.clone()]); - - assert_eq!( - access.settlement_info(0)?.unwrap(), - Settlement { - computation_hash, - final_state, - output_merkle, - output_proof - }, - "settlement info of epoch 0 should match" - ); - - Ok(()) - } -} diff --git a/cartesi-rollups/node/state-manager/src/rollups_machine.rs b/cartesi-rollups/node/state-manager/src/rollups_machine.rs deleted file mode 100644 index 2c6473b23..000000000 --- a/cartesi-rollups/node/state-manager/src/rollups_machine.rs +++ /dev/null @@ -1,212 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use std::path::{Path, PathBuf}; - -use cartesi_prt_core::machine::constants::{ - CHECKPOINT_ADDRESS, LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, - LOG2_UARCH_SPAN_TO_BARCH, -}; - -use crate::{CommitmentLeaf, Proof}; -use cartesi_machine::{ - config::runtime::RuntimeConfig, - constants::{ar::TX_START, break_reason, machine::HASH_TREE_LOG2_ROOT_SIZE}, - error::{MachineError, MachineResult}, - machine::Machine, - types::{ - Hash, - cmio::{CmioRequest, CmioResponseReason, ManualReason}, - }, -}; - -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum StoreError { - #[error(transparent)] - MachineError(#[from] MachineError), - - #[error("Failed to cleanup partial store {fs_err}, caused by {machine_err}")] - CleanupError { - machine_err: MachineError, - fs_err: std::io::Error, - }, -} - -// gap of each leaf in the commitment tree, should use the same value as ArbitrationConstants.sol:log2step(0) -pub const LOG2_STRIDE: u64 = 44; - -pub const BIG_STEPS_IN_STRIDE: u64 = 1 << (LOG2_STRIDE - LOG2_UARCH_SPAN_TO_BARCH); - -pub const STRIDE_COUNT_IN_INPUT: u64 = - 1 << (LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE); - -pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 - << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - - LOG2_STRIDE); - -pub struct RollupsMachine { - machine: Machine, - epoch_number: u64, - next_input_index_in_epoch: u64, -} - -impl RollupsMachine { - pub fn new( - path: &Path, - epoch_number: u64, - next_input_index_in_epoch: u64, - ) -> MachineResult { - let runtime_config = RuntimeConfig::quiet_console(); - let machine = Machine::load(path, &runtime_config)?; - - Ok(Self { - machine, - epoch_number, - next_input_index_in_epoch, - }) - } - - pub fn epoch(&self) -> u64 { - self.epoch_number - } - - pub fn next_input_index_in_epoch(&self) -> u64 { - self.next_input_index_in_epoch - } - - pub fn finish_epoch(&mut self) { - self.epoch_number += 1; - self.next_input_index_in_epoch = 0; - } - - pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { - let proof = self.machine.proof(TX_START, 5, HASH_TREE_LOG2_ROOT_SIZE)?; - let siblings = Proof::new(proof.sibling_hashes); - let output_merkle = self.machine.read_memory(TX_START, 32)?; - - assert_eq!(output_merkle.len(), 32); - Ok((output_merkle.try_into().unwrap(), siblings)) - } - - pub fn state_hash(&mut self) -> MachineResult { - self.machine.root_hash() - } - - pub fn process_input( - &mut self, - data: &[u8], - ) -> MachineResult<(Vec, ManualReason)> { - assert!(self.machine.iflags_y()?); - assert!(matches!( - self.machine.receive_cmio_request()?, - CmioRequest::Manual(ManualReason::RxAccepted { .. }) - )); - - let checkpoint_hash = self.machine.root_hash()?; - self.feed_input(data, &checkpoint_hash)?; - self.run_machine(BIG_STEPS_IN_STRIDE)?; - - let mut state_hashes = Vec::with_capacity(1 << 20); - let mut i: u64 = 0; - - while !self.machine.iflags_y()? { - let hash = self.machine.root_hash()?; - state_hashes.push(CommitmentLeaf { - hash, - repetitions: 1, - }); - i += 1; - - self.run_machine(BIG_STEPS_IN_STRIDE)?; - } - - self.next_input_index_in_epoch += 1; - - match self.machine.receive_cmio_request()? { - CmioRequest::Manual(reason @ ManualReason::RxAccepted { .. }) => { - let fixed_point_hash = self.machine.root_hash()?; - state_hashes.push(CommitmentLeaf { - hash: fixed_point_hash, - repetitions: STRIDE_COUNT_IN_INPUT - i, - }); - - Ok((state_hashes, reason)) - } - - CmioRequest::Manual(reason) => { - state_hashes.push(CommitmentLeaf { - hash: checkpoint_hash, - repetitions: STRIDE_COUNT_IN_INPUT - i, - }); - - Ok((state_hashes, reason)) - } - _ => { - unreachable!("machine should be manually yielded"); - } - } - } - - fn feed_input(&mut self, input: &[u8], checkpoint_hash: &Hash) -> MachineResult<()> { - self.machine - .write_memory(CHECKPOINT_ADDRESS, checkpoint_hash)?; - self.machine - .send_cmio_response(CmioResponseReason::Advance, input) - } - - fn run_machine(&mut self, cycles: u64) -> MachineResult<()> { - let mcycle = self.machine.mcycle()?; - - loop { - let reason = self.machine.run(mcycle + cycles)?; - match reason { - break_reason::YIELDED_AUTOMATICALLY | break_reason::YIELDED_SOFTLY => continue, - - break_reason::YIELDED_MANUALLY | break_reason::REACHED_TARGET_MCYCLE => { - break Ok(()); - } - - _ => panic!("machine returned invalid `break_reason` {reason}"), - } - } - } - - pub fn increment_input(&mut self) { - self.next_input_index_in_epoch += 1; - } - - pub fn store_if_needed( - &mut self, - snapshots_path: &Path, - ) -> Result<(PathBuf, Hash), StoreError> { - let state_hash = self.state_hash()?; - let dest_machine_path = machine_store_path(snapshots_path, &state_hash); - - if !dest_machine_path.exists() { - let machine_status = self.machine.store(&dest_machine_path); - - if let Err(machine_err) = machine_status { - // cleanup partial store before returning error. - let fs_status = std::fs::remove_dir_all(&dest_machine_path); - - // combine errors - if let Err(fs_err) = fs_status { - return Err(StoreError::CleanupError { - machine_err, - fs_err, - }); - } else { - return Err(machine_err.into()); - } - } - } - - Ok((dest_machine_path, state_hash)) - } -} - -fn machine_store_path(snapshots_path: &Path, state_hash: &cartesi_machine::types::Hash) -> PathBuf { - snapshots_path.join(format!("0x{}", hex::encode(state_hash))) -} diff --git a/cartesi-rollups/node/state-manager/src/sql/consensus_data.rs b/cartesi-rollups/node/state-manager/src/sql/consensus_data.rs deleted file mode 100644 index 10feea15e..000000000 --- a/cartesi-rollups/node/state-manager/src/sql/consensus_data.rs +++ /dev/null @@ -1,685 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use crate::{ - Epoch, Input, InputId, - state_manager::{Result, StateAccessError}, -}; - -use alloy::{ - hex::{FromHex, ToHexExt}, - primitives::Address, -}; -use rusqlite::{OptionalExtension, params}; - -fn convert_row_to_epoch(row: &rusqlite::Row) -> rusqlite::Result { - let tournament_str: String = row.get(2)?; - Ok(Epoch { - epoch_number: row.get(0)?, - input_index_boundary: row.get(1)?, - root_tournament: Address::from_hex(tournament_str).unwrap(), - block_created_number: row.get(3)?, - }) -} - -// -// Last Processed -// - -pub fn update_last_processed_block(conn: &rusqlite::Connection, block: u64) -> Result<()> { - let previous = last_processed_block(conn)?; - - if previous >= block { - return Err(StateAccessError::InconsistentLastProcessed { - last: previous, - provided: block, - }); - } - - conn.execute( - "UPDATE latest_processed SET block = ?1 WHERE id = 1", - params![block], - ) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn last_processed_block(conn: &rusqlite::Connection) -> Result { - Ok(conn - .query_row( - "\ - SELECT block FROM latest_processed WHERE id = 1 - ", - [], - |r| r.get(0), - ) - .map_err(anyhow::Error::from)?) -} - -// -// Inputs -// - -fn validate_insert(current: &Option, next: &InputId) -> bool { - match ¤t { - Some(i) if !i.validate_next(next) => false, - None if next.input_index_in_epoch != 0 => false, - _ => true, - } -} - -pub fn insert_inputs<'a>( - conn: &rusqlite::Connection, - inputs: impl Iterator, -) -> Result<()> { - let mut inputs = inputs.peekable(); - if inputs.peek().is_none() { - return Ok(()); - } - - let mut current_input = last_input(conn)?; - - let mut stmt = insert_input_statement(conn)?; - for input in inputs { - if !validate_insert(¤t_input, &input.id) { - return Err(StateAccessError::InconsistentInput { - previous: current_input, - provided: input.id.clone(), - }); - } - - stmt.execute(params![ - input.id.epoch_number, - input.id.input_index_in_epoch, - input.data - ]) - .map_err(anyhow::Error::from)?; - - current_input = Some(input.id.clone()); - } - - Ok(()) -} - -fn insert_input_statement(conn: &rusqlite::Connection) -> Result> { - Ok(conn - .prepare( - "\ - INSERT INTO inputs (epoch_number, input_index_in_epoch, input) VALUES (?1, ?2, ?3) - ", - ) - .map_err(anyhow::Error::from)?) -} - -pub fn last_input(conn: &rusqlite::Connection) -> Result> { - let mut stmt = conn - .prepare( - "\ - SELECT epoch_number, input_index_in_epoch FROM inputs - ORDER BY epoch_number DESC, input_index_in_epoch DESC - LIMIT 1 - ", - ) - .map_err(anyhow::Error::from)?; - - Ok(stmt - .query_row([], |row| { - Ok(InputId { - epoch_number: row.get(0)?, - input_index_in_epoch: row.get(1)?, - }) - }) - .optional() - .map_err(anyhow::Error::from)?) -} - -pub fn input(conn: &rusqlite::Connection, id: &InputId) -> Result> { - let mut stmt = conn - .prepare( - "\ - SELECT * FROM inputs - WHERE epoch_number = ?1 AND input_index_in_epoch = ?2 - ", - ) - .map_err(anyhow::Error::from)?; - - let i = stmt - .query_row(params![id.epoch_number, id.input_index_in_epoch], |row| { - Ok(Input { - id: id.clone(), - data: row.get(2)?, - }) - }) - .optional() - .map_err(anyhow::Error::from)?; - - Ok(i) -} - -pub fn inputs(conn: &rusqlite::Connection, epoch_number: u64) -> Result>> { - let mut stmt = conn - .prepare( - "\ - SELECT input FROM inputs - WHERE epoch_number = ?1 - ORDER BY input_index_in_epoch ASC - ", - ) - .map_err(anyhow::Error::from)?; - - let query = stmt - .query_map([epoch_number], |r| r.get(0)) - .map_err(anyhow::Error::from)?; - - let mut res = vec![]; - for row in query { - res.push(row.map_err(anyhow::Error::from)?); - } - - Ok(res) -} - -pub fn input_count(conn: &rusqlite::Connection, epoch_number: u64) -> Result { - Ok(conn - .query_row( - "\ - SELECT MAX(input_index_in_epoch) FROM inputs WHERE epoch_number = ?1 - ", - [epoch_number], - |row| { - let x: Option = row.get(0)?; - Ok(x.map(|x: u64| x + 1).unwrap_or(0)) - }, - ) - .map_err(anyhow::Error::from)?) -} - -// -// Epochs -// - -pub fn insert_epochs<'a>( - conn: &rusqlite::Connection, - epochs: impl Iterator, -) -> Result<()> { - let mut epochs = epochs.peekable(); - if epochs.peek().is_none() { - return Ok(()); - } - - let mut next_epoch = epoch_count(conn)?; - - let mut stmt = insert_epoch_statement(conn)?; - for epoch in epochs { - if epoch.epoch_number != next_epoch { - return Err(StateAccessError::InconsistentEpoch { - expected: next_epoch, - provided: epoch.epoch_number, - }); - } - - stmt.execute(params![ - epoch.epoch_number, - epoch.input_index_boundary, - epoch.root_tournament.encode_hex(), - epoch.block_created_number - ]) - .map_err(anyhow::Error::from)?; - - next_epoch += 1; - } - Ok(()) -} - -fn insert_epoch_statement(conn: &rusqlite::Connection) -> Result> { - Ok(conn.prepare( - "\ - INSERT INTO epochs (epoch_number, input_index_boundary, root_tournament, block_created_number) VALUES (?1, ?2, ?3, ?4) - ", - ).map_err(anyhow::Error::from)?) -} - -pub fn last_sealed_epoch(conn: &rusqlite::Connection) -> Result> { - let mut stmt = conn - .prepare( - r#" - SELECT epoch_number, input_index_boundary, root_tournament, block_created_number - FROM epochs - ORDER BY epoch_number DESC - LIMIT 1 - "#, - ) - .map_err(anyhow::Error::from)?; - - Ok(stmt - .query_row([], convert_row_to_epoch) - .optional() - .map_err(anyhow::Error::from)?) -} - -pub fn epoch(conn: &rusqlite::Connection, epoch_number: u64) -> Result> { - let mut stmt = conn - .prepare_cached( - r#" - SELECT epoch_number, input_index_boundary, root_tournament, block_created_number - FROM epochs - WHERE epoch_number = ?1 - "#, - ) - .map_err(anyhow::Error::from)?; - - let e = stmt - .query_row(params![epoch_number], convert_row_to_epoch) - .optional() - .map_err(anyhow::Error::from)?; - - Ok(e) -} - -pub fn epoch_count(conn: &rusqlite::Connection) -> Result { - Ok(conn - .query_row( - "\ - SELECT MAX(epoch_number) FROM epochs - ", - [], - |row| { - let x: Option = row.get(0)?; - Ok(x.map(|x: u64| x + 1).unwrap_or(0)) - }, - ) - .map_err(anyhow::Error::from)?) -} - -// -// Tests -// - -#[cfg(test)] -mod last_processed_block_tests { - use super::*; - use crate::sql::test_helper; - - #[test] - fn test_last_processed_block() { - let (_handle, conn) = test_helper::setup_db(); - - assert!(matches!( - update_last_processed_block(&conn, 0), - Err(StateAccessError::InconsistentLastProcessed { - last: 0, - provided: 0 - }) - )); - - update_last_processed_block(&conn, 1).unwrap(); - assert!(matches!(last_processed_block(&conn), Ok(1))); - - assert!(matches!( - update_last_processed_block(&conn, 0), - Err(StateAccessError::InconsistentLastProcessed { - last: 1, - provided: 0 - }) - )); - assert!(matches!( - update_last_processed_block(&conn, 1), - Err(StateAccessError::InconsistentLastProcessed { - last: 1, - provided: 1 - }) - )); - - update_last_processed_block(&conn, 200).unwrap(); - assert!(matches!(last_processed_block(&conn), Ok(200))); - } -} - -#[cfg(test)] -mod inputs_tests { - use crate::sql::test_helper; - - use super::*; - - #[test] - fn test_empty() { - let (_handle, conn) = test_helper::setup_db(); - assert!(matches!(last_input(&conn), Ok(None))); - assert!(matches!(input(&conn, &InputId::default()), Ok(None))); - } - - #[test] - fn test_insert() { - let (_handle, conn) = test_helper::setup_db(); - let data = vec![1]; - - assert!(matches!( - insert_inputs( - &conn, - [&Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - data: data.clone(), - }] - .into_iter(), - ), - Ok(()) - )); - - assert!(matches!( - last_input(&conn), - Ok(Some(InputId { - epoch_number: 0, - input_index_in_epoch: 0 - })) - )); - assert!(matches!( - input( - &conn, - &InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - ), - Ok(Some(Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - .. - })) - )); - - assert!(matches!( - insert_inputs( - &conn, - [ - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 1 - }, - data: data.clone(), - }, - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 2 - }, - data: data.clone(), - }, - &Input { - id: InputId { - epoch_number: 1, - input_index_in_epoch: 0 - }, - data: data.clone(), - }, - &Input { - id: InputId { - epoch_number: 3, - input_index_in_epoch: 0 - }, - data: data.clone(), - } - ] - .into_iter(), - ), - Ok(()) - )); - assert!(matches!( - last_input(&conn), - Ok(Some(InputId { - epoch_number: 3, - input_index_in_epoch: 0 - })) - )); - assert!(matches!( - input( - &conn, - &InputId { - epoch_number: 0, - input_index_in_epoch: 2 - }, - ), - Ok(Some(Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 2 - }, - .. - })) - )); - } - - #[test] - fn test_inconsistent_insert() { - let (_handle, conn) = test_helper::setup_db(); - let data = vec![1]; - - assert!( - insert_inputs( - &conn, - [&Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 1 - }, - data: data.clone(), - }] - .into_iter(), - ) - .is_err() - ); - assert!( - insert_inputs( - &conn, - [&Input { - id: InputId { - epoch_number: 1, - input_index_in_epoch: 1 - }, - data: data.clone(), - }] - .into_iter(), - ) - .is_err() - ); - - assert!(matches!(last_input(&conn), Ok(None))); - assert!(matches!( - input( - &conn, - &InputId { - epoch_number: 0, - input_index_in_epoch: 1 - }, - ), - Ok(None) - )); - - assert!( - insert_inputs( - &conn, - [ - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - data: data.clone(), - }, - &Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 2 - }, - data: data.clone(), - }, - ] - .into_iter(), - ) - .is_err() - ); - assert!(matches!( - last_input(&conn), - Ok(Some(InputId { - epoch_number: 0, - input_index_in_epoch: 0 - })) - )); - assert!(matches!( - input( - &conn, - &InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - ), - Ok(Some(Input { - id: InputId { - epoch_number: 0, - input_index_in_epoch: 0 - }, - .. - })) - )); - assert!(matches!(input_count(&conn, 0,), Ok(1))); - } -} - -#[cfg(test)] -mod epochs_tests { - use super::*; - use crate::sql::test_helper; - - #[test] - fn test_epoch() { - let (_handle, conn) = test_helper::setup_db(); - - assert!(matches!(epoch_count(&conn), Ok(0))); - - assert!(matches!( - insert_epochs( - &conn, - [&Epoch { - epoch_number: 1, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 3, - }] - .into_iter(), - ), - Err(StateAccessError::InconsistentEpoch { - expected: 0, - provided: 1 - }) - )); - assert!(matches!(epoch_count(&conn), Ok(0))); - - assert!(matches!( - insert_epochs( - &conn, - [&Epoch { - epoch_number: 0, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 3, - }] - .into_iter(), - ), - Ok(()) - )); - assert!(matches!(epoch_count(&conn), Ok(1))); - - assert!(matches!( - insert_epochs( - &conn, - [&Epoch { - epoch_number: 0, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 3, - }] - .into_iter(), - ), - Err(StateAccessError::InconsistentEpoch { - expected: 1, - provided: 0 - }) - )); - assert!(matches!(epoch_count(&conn), Ok(1))); - - let x: Vec<_> = (1..128) - .map(|i| Epoch { - epoch_number: i, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: i * 2, - }) - .collect(); - assert!(matches!(insert_epochs(&conn, x.iter()), Ok(()))); - assert!(matches!(epoch_count(&conn), Ok(128))); - - assert!(matches!( - insert_epochs( - &conn, - [ - &Epoch { - epoch_number: 128, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 256, - }, - &Epoch { - epoch_number: 129, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 258, - }, - &Epoch { - epoch_number: 131, - input_index_boundary: 0, - root_tournament: Address::ZERO, - block_created_number: 262, - } - ] - .into_iter(), - ), - Err(StateAccessError::InconsistentEpoch { - expected: 130, - provided: 131 - }) - )); - assert!(matches!(epoch_count(&conn), Ok(130))); - - let tournament_address = - Address::from_hex("0x8dA443F84fEA710266C8eB6bC34B71702d033EF2").unwrap(); - assert!(matches!(epoch(&conn, 130), Ok(None))); - assert!(matches!( - insert_epochs( - &conn, - [&Epoch { - epoch_number: 130, - input_index_boundary: 99, - root_tournament: tournament_address, - block_created_number: 260, - }] - .into_iter(), - ), - Ok(()) - )); - assert!(matches!( - epoch(&conn, 130), - Ok(Some(Epoch { - epoch_number: 130, - input_index_boundary: 99, - block_created_number: 260, - .. - })) - )); - } -} diff --git a/cartesi-rollups/node/state-manager/src/sql/migrations.rs b/cartesi-rollups/node/state-manager/src/sql/migrations.rs deleted file mode 100644 index 65a8c0087..000000000 --- a/cartesi-rollups/node/state-manager/src/sql/migrations.rs +++ /dev/null @@ -1,12 +0,0 @@ -use lazy_static::lazy_static; -use rusqlite::Connection; -use rusqlite_migration::{M, Migrations}; - -lazy_static! { - pub static ref MIGRATIONS: Migrations<'static> = - Migrations::new(vec![M::up(include_str!("migrations.sql")),]); -} - -pub fn migrate_to_latest(conn: &mut Connection) -> Result<(), rusqlite_migration::Error> { - MIGRATIONS.to_latest(conn) -} diff --git a/cartesi-rollups/node/state-manager/src/sql/migrations.sql b/cartesi-rollups/node/state-manager/src/sql/migrations.sql deleted file mode 100644 index 9006e6531..000000000 --- a/cartesi-rollups/node/state-manager/src/sql/migrations.sql +++ /dev/null @@ -1,73 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -CREATE TABLE IF NOT EXISTS settlement_info ( - epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), - computation_hash BLOB NOT NULL, - final_state BLOB NOT NULL, - output_merkle BLOB NOT NULL, - output_proof BLOB NOT NULL -); - -CREATE TABLE IF NOT EXISTS epochs ( - epoch_number INTEGER NOT NULL PRIMARY KEY CHECK (epoch_number >= 0), - input_index_boundary INTEGER NOT NULL, - root_tournament TEXT NOT NULL, - block_created_number INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS inputs ( - epoch_number INTEGER NOT NULL CHECK (epoch_number >= 0), - input_index_in_epoch INTEGER NOT NULL, - input BLOB NOT NULL, - PRIMARY KEY (epoch_number, input_index_in_epoch) -); - -CREATE TABLE IF NOT EXISTS latest_processed ( - id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), - block INTEGER NOT NULL CHECK (block >= 0) -); -INSERT OR IGNORE INTO latest_processed (id, block) - VALUES (1, 0); - -CREATE TABLE IF NOT EXISTS machine_state_hashes ( - epoch_number INTEGER NOT NULL, - input_number INTEGER NOT NULL, - hash_index INTEGER NOT NULL, - repetitions INTEGER NOT NULL CHECK (repetitions > 0), - machine_state_hash BLOB NOT NULL, - PRIMARY KEY (epoch_number, input_number, hash_index) -); - -CREATE TABLE IF NOT EXISTS template_machine ( - id INTEGER PRIMARY KEY CHECK (id = 1), - state_hash BLOB NOT NULL - UNIQUE - REFERENCES machine_state_snapshots (state_hash) - ON DELETE RESTRICT -) WITHOUT ROWID; - -CREATE TABLE IF NOT EXISTS machine_state_snapshots ( - state_hash BLOB NOT NULL PRIMARY KEY, - file_path TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS epoch_snapshot_info ( - epoch_number INTEGER NOT NULL CHECK (epoch_number >= 0), - input_number INTEGER NOT NULL CHECK (input_number >= 0), - state_hash BLOB NOT NULL, - - PRIMARY KEY (epoch_number, input_number), - - FOREIGN KEY (state_hash) - REFERENCES machine_state_snapshots (state_hash) - ON UPDATE CASCADE - ON DELETE RESTRICT -); - --- garbage collect -CREATE TRIGGER IF NOT EXISTS trg_delete_snapshot_files -AFTER DELETE ON machine_state_snapshots -BEGIN - SELECT fs_delete_dir(OLD.file_path); -END; diff --git a/cartesi-rollups/node/state-manager/src/sql/mod.rs b/cartesi-rollups/node/state-manager/src/sql/mod.rs deleted file mode 100644 index c9481e55e..000000000 --- a/cartesi-rollups/node/state-manager/src/sql/mod.rs +++ /dev/null @@ -1,151 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pub mod consensus_data; -pub mod migrations; -pub mod rollup_data; - -#[cfg(test)] -pub(crate) mod test_helper; - -use crate::{rollups_machine::RollupsMachine, state_manager::Result}; -use anyhow::Context; -use rusqlite::{Connection, functions::FunctionFlags}; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -pub fn fs_delete_dir( - ctx: &rusqlite::functions::Context, -) -> std::result::Result { - let path: String = ctx.get(0)?; - std::fs::remove_dir_all(&path).map_err(|e| rusqlite::Error::UserFunctionError(Box::new(e)))?; - Ok(rusqlite::types::Null) -} - -fn set_genesis(connection: &Connection, block_number: u64) -> Result<()> { - let last_processed = consensus_data::last_processed_block(connection)?; - - if block_number > last_processed { - consensus_data::update_last_processed_block(connection, block_number)?; - } - Ok(()) -} - -fn set_initial_machine( - connection: &mut Connection, - state_dir: &Path, - source_machine_path: &Path, -) -> Result<()> { - assert!( - state_dir.is_dir(), - "`{}` should be a directory", - state_dir.display() - ); - assert!( - source_machine_path.is_dir(), - "machine path `{}` must be an existing directory", - source_machine_path.display() - ); - - let mut machine = RollupsMachine::new(source_machine_path, 0, 0)?; - - let (dest_machine_path, state_hash) = { - let snapshots_path = snapshots_path(state_dir); - machine - .store_if_needed(&snapshots_path) - .map_err(anyhow::Error::from)? - }; - - let tx = connection.transaction().map_err(anyhow::Error::from)?; - rollup_data::insert_snapshot(&tx, 0, 0, &state_hash, &dest_machine_path)?; - rollup_data::insert_template_machine(&tx, &state_hash)?; - tx.commit().map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn set_scalar_function(connection: &Connection) -> Result<()> { - connection - .create_scalar_function( - "fs_delete_dir", - 1, - FunctionFlags::SQLITE_UTF8, - fs_delete_dir, - ) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn create_connection(state_dir: &Path) -> Result { - let db_path = db_path(state_dir); - let connection = Connection::open(db_path).map_err(anyhow::Error::from)?; - connection - .busy_timeout(std::time::Duration::from_secs(10)) - .map_err(anyhow::Error::from)?; - - set_scalar_function(&connection)?; - - Ok(connection) -} - -pub fn migrate( - state_dir: &Path, - initial_machine_path: &Path, - genesis_block_number: u64, -) -> Result { - create_directory_structure(state_dir)?; - let mut connection = create_connection(state_dir)?; - - // Enable WAL mode for concurrent access - connection - .query_row("PRAGMA journal_mode=WAL;", [], |_| Ok(())) - .map_err(anyhow::Error::from)?; - - migrations::migrate_to_latest(&mut connection).map_err(anyhow::Error::from)?; - set_genesis(&connection, genesis_block_number)?; - set_initial_machine(&mut connection, state_dir, initial_machine_path)?; - - Ok(connection) -} - -// -// Directory structure -// - -pub fn create_empty_state_dir_if_needed(state_dir: &Path) -> Result<()> { - fs::create_dir_all(state_dir).with_context(|| format!("creating `{}`", state_dir.display()))?; - Ok(()) -} - -pub fn db_path(state_dir: &Path) -> PathBuf { - state_dir.to_owned().join("db.sqlite3") -} - -pub fn snapshots_path(state_dir: &Path) -> PathBuf { - state_dir.to_owned().join("snapshots") -} - -pub fn create_directory_structure(state_dir: &Path) -> Result<()> { - create_empty_state_dir_if_needed(state_dir)?; - - let snapshots_path = snapshots_path(state_dir); - - fs::create_dir_all(&snapshots_path) - .with_context(|| format!("creating `{}`", &snapshots_path.display()))?; - - Ok(()) -} - -fn epoch_dir(state_dir: &Path, epoch_number: u64) -> PathBuf { - state_dir.join(epoch_number.to_string()) -} - -pub fn create_epoch_dir(state_dir: &Path, epoch_number: u64) -> Result { - let path = epoch_dir(state_dir, epoch_number); - fs::create_dir_all(&path).with_context(|| format!("creating `{}`", &path.display()))?; - - Ok(path) -} diff --git a/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs b/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs deleted file mode 100644 index 4ae0002f7..000000000 --- a/cartesi-rollups/node/state-manager/src/sql/rollup_data.rs +++ /dev/null @@ -1,531 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use std::path::PathBuf; - -use crate::{CommitmentLeaf, InputId, Proof, Settlement, state_manager::Result}; - -use cartesi_machine::types::Hash; - -use rusqlite::{Connection, OptionalExtension, params}; - -fn convert_row_to_commitment_leaf(r: &rusqlite::Row) -> rusqlite::Result { - let hash: Hash = { - let vec: Vec = r.get(0)?; - vec.try_into() - .expect("machine_state_hash should have 32 bytes") - }; - - let repetitions: u64 = r.get(1)?; - - Ok(CommitmentLeaf { hash, repetitions }) -} - -fn convert_row_to_settlement(row: &rusqlite::Row) -> rusqlite::Result { - // computation_hash blob -> [u8;32] -> Digest - let ch_blob: Vec = row.get(0)?; - let ch_arr: [u8; 32] = ch_blob - .try_into() - .expect("computation_hash must be 32 bytes"); - let computation_hash = ch_arr.into(); - - // final_state blob -> [u8;32] - let fs_blob: Vec = row.get(1)?; - let final_state: Hash = fs_blob.try_into().expect("final_state must be 32 bytes"); - - // output_merkle blob -> [u8;32] - let om_blob: Vec = row.get(2)?; - let output_merkle: Hash = om_blob.try_into().expect("output_merkle must be 32 bytes"); - - // output_proof blob -> Proof - let proof_blob: Vec = row.get(3)?; - let output_proof = Proof::from_flattened(proof_blob); - - Ok(Settlement { - computation_hash, - final_state, - output_merkle, - output_proof, - }) -} - -pub fn get_all_commitments(conn: &Connection, epoch_number: u64) -> Result> { - let mut stmt = conn - .prepare_cached( - r#" - SELECT machine_state_hash, repetitions - FROM machine_state_hashes - WHERE epoch_number = ?1 - ORDER BY - input_number ASC, - hash_index ASC - "#, - ) - .map_err(anyhow::Error::from)?; - - let rows = stmt - .query_map(params![epoch_number], convert_row_to_commitment_leaf) - .map_err(anyhow::Error::from)?; - - let res = rows - .collect::>>() - .map_err(anyhow::Error::from)?; - Ok(res) -} - -pub fn insert_state_hashes_for_input( - conn: &Connection, - epoch_number: u64, - input_number: u64, - leafs: &[CommitmentLeaf], -) -> Result<()> { - let mut stmt = conn - .prepare_cached( - r#" - INSERT INTO machine_state_hashes - (epoch_number, input_number, hash_index, repetitions, machine_state_hash) - VALUES (?1, ?2, ?3, ?4, ?5) - "#, - ) - .map_err(anyhow::Error::from)?; - - for (i, leaf) in leafs.iter().enumerate() { - let count = stmt - .execute(params![ - epoch_number, - input_number, - i, - leaf.repetitions, - leaf.hash.as_ref(), - ]) - .map_err(anyhow::Error::from)?; - - assert_eq!( - count, 1, - "expected exactly one row to be inserted into machine_state_hashes" - ); - } - - Ok(()) -} - -pub fn settlement_info(conn: &Connection, epoch_number: u64) -> Result> { - let mut stmt = conn - .prepare_cached( - r#" - SELECT computation_hash, final_state, output_merkle, output_proof - FROM settlement_info - WHERE epoch_number = ?1 - "#, - ) - .map_err(anyhow::Error::from)?; - - let settlement = stmt - .query_row(params![epoch_number], convert_row_to_settlement) - .optional() - .map_err(anyhow::Error::from)?; - - Ok(settlement) -} - -pub fn insert_settlement_info( - conn: &Connection, - settlement: &Settlement, - epoch_number: u64, -) -> Result<()> { - let mut stmt = conn - .prepare_cached( - r#" - INSERT INTO settlement_info - (epoch_number, computation_hash, final_state, output_merkle, output_proof) - VALUES (?1, ?2, ?3, ?4, ?5) - "#, - ) - .map_err(anyhow::Error::from)?; - - let count = stmt - .execute(params![ - epoch_number, - settlement.computation_hash.data(), - &settlement.final_state, - &settlement.output_merkle, - &settlement.output_proof.flatten(), - ]) - .map_err(anyhow::Error::from)?; - - assert_eq!(count, 1, "expected exactly one row inserted"); - Ok(()) -} - -pub fn insert_template_machine( - conn: &Connection, - state_hash: &cartesi_machine::types::Hash, -) -> Result<()> { - let mut sttm = conn - .prepare_cached( - r#" - INSERT OR IGNORE INTO template_machine (id, state_hash) - VALUES(1, ?1) - "#, - ) - .map_err(anyhow::Error::from)?; - sttm.execute(rusqlite::params![state_hash]) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn insert_snapshot( - conn: &Connection, - epoch_number: u64, - input_number: u64, - state_hash: &cartesi_machine::types::Hash, - dest_dir: &std::path::Path, -) -> Result<()> { - let mut sttm = conn - .prepare_cached( - r#" - INSERT INTO machine_state_snapshots(state_hash, file_path) - VALUES(?1, ?2) - ON CONFLICT(state_hash) DO NOTHING - "#, - ) - .map_err(anyhow::Error::from)?; - sttm.execute(rusqlite::params![state_hash, dest_dir.to_string_lossy()]) - .map_err(anyhow::Error::from)?; - - let mut sttm = conn - .prepare_cached( - r#" - INSERT INTO epoch_snapshot_info(epoch_number, input_number, state_hash) - VALUES(?1, ?2, ?3) - ON CONFLICT(epoch_number, input_number) DO NOTHING - "#, - ) - .map_err(anyhow::Error::from)?; - sttm.execute(rusqlite::params![epoch_number, input_number, state_hash]) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn gc_old_epochs(conn: &Connection, max_epoch: u64) -> Result<()> { - conn.execute( - r#" - DELETE FROM epoch_snapshot_info - WHERE epoch_number <= ?1 - "#, - [max_epoch], - ) - .map_err(anyhow::Error::from)?; - - conn.execute_batch( - r#" - DELETE FROM machine_state_snapshots - WHERE state_hash NOT IN ( - SELECT state_hash FROM epoch_snapshot_info - UNION - SELECT state_hash FROM template_machine - ); - "#, - ) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn gc_previous_advances(conn: &Connection, epoch: u64, input_anchor: u64) -> Result<()> { - conn.execute( - r#" - DELETE FROM epoch_snapshot_info - WHERE epoch_number = ?1 AND (input_number != ?2 AND input_number != 0) - "#, - [epoch, input_anchor], - ) - .map_err(anyhow::Error::from)?; - - conn.execute_batch( - r#" - DELETE FROM machine_state_snapshots - WHERE state_hash NOT IN ( - SELECT state_hash FROM epoch_snapshot_info - UNION - SELECT state_hash FROM template_machine - ); - "#, - ) - .map_err(anyhow::Error::from)?; - - Ok(()) -} - -pub fn next_input_to_be_processed(conn: &Connection) -> Result { - let mut stmt = conn - .prepare_cached( - r#" - SELECT epoch_number, input_number - FROM epoch_snapshot_info - ORDER BY - epoch_number DESC, - input_number DESC - LIMIT 1 - "#, - ) - .map_err(anyhow::Error::from)?; - - let (epoch_number, input_index_in_epoch): (u64, u64) = stmt - .query_row([], |row| Ok((row.get(0)?, row.get(1)?))) - .expect("there should at least be a single latest processed"); - - Ok(InputId { - epoch_number, - input_index_in_epoch, - }) -} - -pub fn latest_snapshot_path(conn: &Connection) -> Result<(PathBuf, u64, u64)> { - let mut stmt = conn - .prepare_cached( - r#" - SELECT s.file_path, e.epoch_number, e.input_number - FROM epoch_snapshot_info AS e - JOIN machine_state_snapshots AS s - ON s.state_hash = e.state_hash - ORDER BY - e.epoch_number DESC, - e.input_number DESC - LIMIT 1 - "#, - ) - .map_err(anyhow::Error::from)?; - - let (path, epoch, input): (String, u64, u64) = stmt - .query_row([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?))) - .expect("there should at least be a single machine"); - - Ok((path.into(), epoch, input)) -} - -pub fn snapshot_path_for_epoch( - conn: &Connection, - epoch_number: u64, - input_number: u64, -) -> Result> { - let mut stmt = conn - .prepare_cached( - r#" - SELECT s.file_path - FROM epoch_snapshot_info AS e - JOIN machine_state_snapshots AS s - ON s.state_hash = e.state_hash - WHERE e.epoch_number = ?1 AND e.input_number= ?2 - "#, - ) - .map_err(anyhow::Error::from)?; - - Ok(stmt - .query_row([epoch_number, input_number], |row| row.get::<_, String>(0)) - .optional() - .map(|opt| opt.map(PathBuf::from)) - .map_err(anyhow::Error::from)?) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::{CommitmentLeaf, Proof, Settlement, sql::test_helper::*}; - use rusqlite::Connection; - use tempfile::TempDir; - - /// Convenience: count rows in a table. - fn count_rows(conn: &Connection, table: &str) -> u32 { - conn.query_row::(&format!("SELECT COUNT(*) FROM {table}"), [], |r| r.get(0)) - .expect("count should succeed") - } - - #[test] - fn get_all_commitments_single() { - let (_handle, conn) = setup_db(); - let leaf = CommitmentLeaf { - hash: [7; 32], - repetitions: 5, - }; - insert_state_hashes_for_input(&conn, 42, 0, &[leaf.clone()]).unwrap(); - - let fetched = get_all_commitments(&conn, 42).unwrap(); - assert_eq!(fetched, vec![leaf]); - } - - #[test] - fn get_all_commitments_multiple_inputs_ordering() { - let (_handle, conn) = setup_db(); - // Two different input indices; ordering must be input_number ASC, hash_index ASC - let l0 = CommitmentLeaf { - hash: [1; 32], - repetitions: 1, - }; - let l1 = CommitmentLeaf { - hash: [2; 32], - repetitions: 1, - }; - // input 0 - insert_state_hashes_for_input(&conn, 7, 0, &[l0.clone()]).unwrap(); - // input 1 - insert_state_hashes_for_input(&conn, 7, 1, &[l1.clone()]).unwrap(); - - let all = get_all_commitments(&conn, 7).unwrap(); - assert_eq!(all, vec![l0, l1]); - } - - #[test] - fn get_all_commitments_empty_epoch_returns_empty_vec() { - let (_handle, conn) = setup_db(); - let res = get_all_commitments(&conn, 999).unwrap(); - assert!(res.is_empty()); - } - - #[test] - fn insert_state_hashes_empty_slice_is_noop() { - let (_handle, conn) = setup_db(); - - let before = count_rows(&conn, "machine_state_hashes"); - insert_state_hashes_for_input(&conn, 1, 0, &[]).unwrap(); - let after = count_rows(&conn, "machine_state_hashes"); - - assert_eq!(before, after); - } - - #[test] - fn insert_state_hashes_duplicate_primary_key_fails() { - let (_handle, conn) = setup_db(); - - let leaves = [CommitmentLeaf { - hash: [9; 32], - repetitions: 1, - }]; - - // first insert succeeds - insert_state_hashes_for_input(&conn, 2, 0, &leaves).unwrap(); - // second insert should fail due to UNIQUE(epoch,input,hash_index) - let err = insert_state_hashes_for_input(&conn, 2, 0, &leaves).expect_err("should fail"); - assert!(matches!(err, crate::StateAccessError::InnerError(_))); - } - - #[test] - fn settlement_info_none() { - let (_handle, conn) = setup_db(); - assert!(settlement_info(&conn, 1).unwrap().is_none()); - } - - #[test] - fn insert_and_get_settlement_info() { - let (_handle, conn) = setup_db(); - let settlement = Settlement { - computation_hash: [0xAA; 32].into(), - final_state: [0xBB; 32], - output_merkle: [0xCC; 32], - output_proof: Proof::new(vec![[0; 32]]), - }; - insert_settlement_info(&conn, &settlement, 42).unwrap(); - let fetched = settlement_info(&conn, 42).unwrap().unwrap(); - assert_eq!(fetched, settlement); - } - - #[test] - fn insert_settlement_info_duplicate_returns_error() { - let (_handle, conn) = setup_db(); - let settlement = Settlement { - computation_hash: [0x11; 32].into(), - final_state: [0x22; 32], - output_merkle: [0x33; 32], - output_proof: Proof::new(vec![[0; 32]]), - }; - insert_settlement_info(&conn, &settlement, 55).unwrap(); - let err = insert_settlement_info(&conn, &settlement, 55).expect_err("duplicate must fail"); - assert!(matches!(err, crate::StateAccessError::InnerError(_))); - } - - /// Makes a unique temporary directory path for snapshots. - fn tmp_dir() -> TempDir { - TempDir::new().expect("create tempdir") - } - - #[test] - fn insert_snapshot_and_latest_path() { - let (_handle, conn) = setup_db(); - let dir = tmp_dir(); - - insert_snapshot(&conn, 42, 2, &[1u8; 32], dir.path()).unwrap(); - let (p, e, i) = latest_snapshot_path(&conn).unwrap(); - let id = next_input_to_be_processed(&conn).unwrap(); - - assert_eq!(p, dir.path()); - assert_eq!(e, 42); - assert_eq!(i, 2); - - assert_eq!(e, id.epoch_number); - assert_eq!(i, id.input_index_in_epoch); - } - - #[test] - fn snapshot_path_for_epoch_happy_and_none() { - let (_handle, conn) = setup_db(); - let dir1 = tmp_dir(); - let dir2 = tmp_dir(); - - insert_snapshot(&conn, 10, 0, &[1u8; 32], dir1.path()).unwrap(); - insert_snapshot(&conn, 11, 1, &[2u8; 32], dir2.path()).unwrap(); - - // happy path - let p = snapshot_path_for_epoch(&conn, 10, 0).unwrap().unwrap(); - assert_eq!(p, dir1.path()); - - // unknown epoch/input returns None - assert!(snapshot_path_for_epoch(&conn, 99, 99).unwrap().is_none()); - } - - #[test] - fn gc_previous_advances_keeps_anchor_input() { - let (_handle, conn) = setup_db(); - let epoch = 5u64; - let hashes: [[u8; 32]; 4] = [[1; 32], [2; 32], [3; 32], [4; 32]]; - let dirs: Vec = (0..4).map(|_| tmp_dir()).collect(); - - for (input, (hash, dir)) in hashes.into_iter().zip(dirs.iter()).enumerate() { - insert_snapshot(&conn, epoch, input as u64, &hash, dir.path()).unwrap(); - } - - // sanity - assert_eq!( - conn.query_row::( - "SELECT COUNT(*) FROM epoch_snapshot_info WHERE epoch_number = ?", - [epoch], - |r| r.get(0), - ) - .unwrap(), - 4 // 4 we inserted - ); - - gc_previous_advances(&conn, epoch, 2).unwrap(); - - // Only template (input 0) + anchor (input 2) - let remaining: u32 = conn - .query_row( - "SELECT COUNT(*) FROM epoch_snapshot_info WHERE epoch_number = ?", - [epoch], - |r| r.get(0), - ) - .unwrap(); - assert_eq!(remaining, 2); - } - - #[test] - fn insert_template_machine_is_idempotent() { - let (_handle, conn) = setup_db(); - let h = [0xFFu8; 32]; - insert_template_machine(&conn, &h).unwrap(); - insert_template_machine(&conn, &h).unwrap(); - assert_eq!(count_rows(&conn, "template_machine"), 1); - } -} diff --git a/cartesi-rollups/node/state-manager/src/state_manager.rs b/cartesi-rollups/node/state-manager/src/state_manager.rs deleted file mode 100644 index 6019858b0..000000000 --- a/cartesi-rollups/node/state-manager/src/state_manager.rs +++ /dev/null @@ -1,101 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use std::path::PathBuf; - -use crate::{CommitmentLeaf, Epoch, Input, InputId, Settlement, rollups_machine::RollupsMachine}; -use cartesi_machine::error::MachineError; -use thiserror::Error; - -pub trait StateManager { - // - // Consensus Data - // - - fn epoch(&mut self, epoch_number: u64) -> Result>; - fn epoch_count(&mut self) -> Result; - fn last_sealed_epoch(&mut self) -> Result>; - fn input(&mut self, id: &InputId) -> Result>; - fn inputs(&mut self, epoch_number: u64) -> Result>>; - fn input_count(&mut self, epoch_number: u64) -> Result; - fn last_input(&mut self) -> Result>; - fn insert_consensus_data<'a>( - &mut self, - last_processed_block: u64, - inputs: impl Iterator, - epochs: impl Iterator, - ) -> Result<()>; - fn latest_processed_block(&mut self) -> Result; - - // - // Rollup Data - // - fn advance_accepted( - &mut self, - machine: &mut RollupsMachine, - leafs: &[CommitmentLeaf], - ) -> Result<()>; - - fn advance_reverted( - &mut self, - machine: &mut RollupsMachine, - leafs: &[CommitmentLeaf], - ) -> Result<()>; - - fn epoch_state_hashes(&mut self, epoch_number: u64) -> Result>; - - fn settlement_info(&mut self, epoch_number: u64) -> Result>; - - fn roll_epoch(&mut self) -> Result<()>; - - fn snapshot(&mut self, epoch_number: u64, input_number: u64) -> Result>; - - fn latest_snapshot(&mut self) -> Result; - fn next_input_id(&mut self) -> Result; - - // - // Directory - // - - fn snapshot_dir(&mut self, epoch_number: u64, input_number: u64) -> Result>; - fn epoch_directory(&mut self, epoch_number: u64) -> Result; -} - -#[derive(Error, Debug)] -pub enum StateAccessError { - #[error("Supplied block `{provided}` is smaller than last processed `{last}`")] - InconsistentLastProcessed { last: u64, provided: u64 }, - - #[error("Supplied Epoch is inconsistent: expected `{expected}`, got `{provided}`")] - InconsistentEpoch { expected: u64, provided: u64 }, - - #[error( - "Supplied Input is inconsistent: previous is `{:?}`, got `{:?}`", - previous, - provided - )] - InconsistentInput { - previous: Option, - provided: InputId, - }, - - #[error("Duplicate entry: `{description}`")] - DuplicateEntry { description: String }, - - #[error("Failed to insert data: `{description}`")] - InsertionFailed { description: String }, - - #[error("Couldn't find data: `{description}`")] - DataNotFound { description: String }, - - #[error("Machine snapshot error")] - MachineError { - #[from] - source: MachineError, - }, - - #[error("Inner error: `{0}`")] - InnerError(#[from] anyhow::Error), -} - -pub type Result = std::result::Result; diff --git a/cartesi-rollups/node/state-manager/src/sync.rs b/cartesi-rollups/node/state-manager/src/sync.rs deleted file mode 100644 index 2446afcdb..000000000 --- a/cartesi-rollups/node/state-manager/src/sync.rs +++ /dev/null @@ -1,144 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use std::{ - ops::ControlFlow, - sync::{Arc, Condvar, Mutex}, - time::Duration, -}; - -#[derive(Debug, Clone)] -pub struct Watch(Arc<(Mutex>>, Condvar)>); - -impl Default for Watch { - fn default() -> Self { - Self(Arc::new(( - Mutex::new(ControlFlow::Continue(())), - Condvar::new(), - ))) - } -} - -impl Watch { - pub fn wait(&self, duration: Duration) -> ControlFlow> { - let (mutex, cvar) = &*self.0; - let (flow, _) = cvar - .wait_timeout_while(mutex.lock().unwrap(), duration, |notification| { - matches!(notification, ControlFlow::Continue(())) - }) - .unwrap(); - - match &*flow { - ControlFlow::Break(e) => ControlFlow::Break(e.clone()), - _ => ControlFlow::Continue(()), // timed‑out OR spurious - } - } - - pub fn notify(&self, err: Arc) { - let (mutex, cvar) = &*self.0; - let mut flow = mutex.lock().unwrap(); - if matches!(*flow, ControlFlow::Continue(_)) { - *flow = ControlFlow::Break(err); - cvar.notify_all(); - } - } - - pub fn err(&self) -> Option> { - let (mutex, _) = &*self.0; - let flow = mutex.lock().unwrap(); - - match &*flow { - ControlFlow::Continue(_) => None, - ControlFlow::Break(e) => Some(e.clone()), - } - } -} - -#[cfg(test)] -mod tests { - use super::Watch; - use anyhow::anyhow; - use std::{ - ops::ControlFlow, - sync::Arc, - thread, - time::{Duration, Instant}, - }; - - /// Helper: create a dummy error wrapped in `Arc`. - fn test_err(msg: &str) -> Arc { - Arc::new(anyhow!(msg.to_owned())) - } - - #[test] - fn fresh_watch_times_out() { - let w = Watch::default(); - - // Wait for a very small timeout; should *not* break. - let res = w.wait(Duration::from_millis(10)); - assert!(matches!(res, ControlFlow::Continue(_))); - assert!(w.err().is_none()); - } - - #[test] - fn notify_breaks_waiter_and_sets_error() { - let w = Watch::default(); - let w2 = w.clone(); - - let handle = thread::spawn(move || { - // Large timeout so only notify can wake us early. - let res = w2.wait(Duration::from_secs(5)); - assert!(matches!(res, ControlFlow::Break(_))); - }); - - // Give the spawned thread a moment to park on the condvar. - thread::sleep(Duration::from_millis(50)); - - let err = test_err("boom"); - w.notify(err.clone()); - - handle.join().unwrap(); - - // Main thread sees the same error. - assert!(Arc::ptr_eq(&w.err().unwrap(), &err)); - } - - #[test] - fn first_error_is_preserved() { - let w = Watch::default(); - - let first = test_err("first"); - let second = test_err("second"); - - w.notify(first.clone()); - w.notify(second); - - let stored = w.err().unwrap(); - assert!(Arc::ptr_eq(&stored, &first)); - } - - #[test] - fn multiple_waiters_all_break() { - let w = Watch::default(); - let mut handles = Vec::new(); - - for _ in 0..4 { - let w_clone = w.clone(); - handles.push(thread::spawn(move || { - let res = w_clone.wait(Duration::from_secs(5)); - assert!(matches!(res, ControlFlow::Break(_))); - })); - } - - // Let all threads block. - thread::sleep(Duration::from_millis(50)); - - // Time how fast they wake up (should be << 5 s). - let t0 = Instant::now(); - w.notify(test_err("stop")); - for h in handles { - h.join().unwrap(); - } - assert!(t0.elapsed() < Duration::from_millis(500)); - } -} diff --git a/cartesi-rollups/node/tests/common/epoch_data.rs b/cartesi-rollups/node/tests/common/epoch_data.rs new file mode 100644 index 000000000..78c24c65c --- /dev/null +++ b/cartesi-rollups/node/tests/common/epoch_data.rs @@ -0,0 +1,33 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The dispute's per-epoch material, held in memory. Inputs are read +//! out of the node database and passed by value; the durable dispute +//! state is the quartet cache, so nothing here needs its own +//! persistence. `work_path` is the epoch's scratch directory for +//! dispute-time machine snapshots (stored by root hash). + +use std::path::PathBuf; + +#[derive(Clone, Debug)] +pub struct Leaf { + pub hash: [u8; 32], + pub repetitions: u64, +} + +#[derive(Debug)] +pub struct EpochData { + inputs: Vec>, + pub work_path: PathBuf, +} + +impl EpochData { + pub fn new(inputs: Vec>, work_path: PathBuf) -> std::io::Result { + std::fs::create_dir_all(&work_path)?; + Ok(Self { inputs, work_path }) + } + + pub fn input(&self, id: u64) -> Option> { + self.inputs.get(id as usize).cloned() + } +} diff --git a/prt/client-rs/core/src/machine/instance.rs b/cartesi-rollups/node/tests/common/instance.rs similarity index 85% rename from prt/client-rs/core/src/machine/instance.rs rename to cartesi-rollups/node/tests/common/instance.rs index 13fe5719e..1ddf94689 100644 --- a/prt/client-rs/core/src/machine/instance.rs +++ b/cartesi-rollups/node/tests/common/instance.rs @@ -1,11 +1,5 @@ -use crate::db::dispute_state_access::DisputeStateAccess; -use crate::machine::constants::{ - BARCH_SPAN_TO_INPUT, CHECKPOINT_ADDRESS, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, - LOG2_UARCH_SPAN_TO_INPUT, UARCH_SPAN_TO_BARCH, -}; -use crate::machine::error::Result; -use cartesi_dave_arithmetic as arithmetic; -use cartesi_dave_merkle::Digest; +use super::epoch_data::EpochData; +use super::machine_error::Result; use cartesi_machine::{ cartesi_machine_sys, config::runtime::RuntimeConfig, @@ -14,8 +8,13 @@ use cartesi_machine::{ types::access_proof::AccessLog, types::{LogType, cmio::CmioResponseReason}, }; +use cartesi_rollups_prt_node::arithmetic; +use cartesi_rollups_prt_node::engine::constants::{ + BARCH_SPAN_TO_INPUT, CHECKPOINT_ADDRESS, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, + LOG2_UARCH_SPAN_TO_INPUT, UARCH_SPAN_TO_BARCH, +}; +use cartesi_rollups_prt_node::merkle::Digest; use log::trace; -use num_traits::{One, ToPrimitive}; use alloy::primitives::U256; use std::path::PathBuf; @@ -53,8 +52,6 @@ impl MachineState { } } -pub type MachineProof = Vec; - pub struct MachineInstance { machine: Machine, _start_cycle: u64, @@ -82,12 +79,12 @@ impl MachineInstance { input_count: 0, cycle: 0, ucycle: 0, - snapshot_path: PathBuf::from(path), + snapshot_path: path, }) } /* - pub fn take_snapshot(&mut self, base_cycle: u64, db: &DisputeStateAccess) -> Result<()> { + pub fn take_snapshot(&mut self, base_cycle: u64, db: &EpochData) -> Result<()> { let mask = arithmetic::max_uint(constants::LOG2_BARCH_SPAN_TO_INPUT); if db.handle_rollups && ((base_cycle & mask) == 0) && !self.is_yielded()? { // don't snapshot a machine state that's freshly fed with input without advance @@ -122,21 +119,19 @@ impl MachineInstance { } */ - pub fn advance_rollups(&mut self, meta_cycle: U256, db: &DisputeStateAccess) -> Result<()> { + pub fn advance_rollups(&mut self, meta_cycle: U256, db: &EpochData) -> Result<()> { assert!(self.is_yielded()?); - let input_count = (meta_cycle >> LOG2_UARCH_SPAN_TO_INPUT) - .to_u64() + let input_count = u64::try_from(meta_cycle >> LOG2_UARCH_SPAN_TO_INPUT) .expect("input count too big to fit in u64"); let cycle = { let c = (meta_cycle >> LOG2_UARCH_SPAN_TO_BARCH) & U256::from(BARCH_SPAN_TO_INPUT); - c.to_u64().expect("cycle too big to fit in u64") + u64::try_from(c).expect("cycle too big to fit in u64") }; - let ucycle = (meta_cycle & U256::from(UARCH_SPAN_TO_BARCH)) - .to_u64() + let ucycle = u64::try_from(meta_cycle & U256::from(UARCH_SPAN_TO_BARCH)) .expect("ucycle too big to fit in u64"); - let snapshot_path = db.work_path.join(format!("{}", self.root_hash()?.to_hex())); + let snapshot_path = db.work_path.join(self.root_hash()?.to_hex()); if !snapshot_path.exists() { self.machine.store(&snapshot_path)?; } @@ -156,6 +151,13 @@ impl MachineInstance { assert!(!self.is_halted()?); self.input_count += 1; + + // `cycle` counts big cycles within the current input window; + // run(u64::MAX) poisoned it, and the next window starts a + // fresh count. Without this, any commitment build or proof + // whose target lies past window 0 overflows the counter. + self.cycle = 0; + self.ucycle = 0; } assert!(self.input_count == input_count); @@ -174,23 +176,37 @@ impl MachineInstance { pub fn new_rollups_advanced_until( path: &str, meta_cycle: U256, - db: &DisputeStateAccess, + db: &EpochData, ) -> Result { - let input_count = (meta_cycle >> LOG2_UARCH_SPAN_TO_INPUT).to_u64().unwrap(); + Self::new_rollups_resumed_until(path, 0, meta_cycle, db) + } + + /// Advances from a machine stored at input boundary `start_input` + /// (a snapshot-source answer; 0 is the epoch start) instead of + /// replaying the whole prefix. + pub fn new_rollups_resumed_until( + path: &str, + start_input: u64, + meta_cycle: U256, + db: &EpochData, + ) -> Result { + let input_count = u64::try_from(meta_cycle >> LOG2_UARCH_SPAN_TO_INPUT).unwrap(); assert!(input_count <= INPUT_SPAN_TO_EPOCH); + assert!(start_input <= input_count, "snapshot past the target"); let mut machine = MachineInstance::new_from_path(path)?; assert!(machine.is_yielded()?); + machine.input_count = start_input; machine.advance_rollups(meta_cycle, db)?; Ok(machine) } - pub fn feed_next_input(&mut self, db: &DisputeStateAccess) -> Result<()> { + pub fn feed_next_input(&mut self, db: &EpochData) -> Result<()> { assert!(self.is_yielded()?); - let input = db.input(self.input_count)?; + let input = db.input(self.input_count); let root_hash = self.root_hash()?; - let new_snapshot_path = db.work_path.join(format!("{}", root_hash.to_hex())); + let new_snapshot_path = db.work_path.join(root_hash.to_hex()); if let Some(input_bin) = input { if !new_snapshot_path.exists() { self.machine.store(&new_snapshot_path)?; @@ -232,10 +248,6 @@ impl MachineInstance { Ok(self.machine.mcycle()?) } - pub fn physical_uarch_cycle(&mut self) -> Result { - Ok(self.machine.ucycle()?) - } - pub fn revert_if_needed(&mut self) -> Result<()> { // revert if needed only when machine yields assert!(self.is_yielded()?); @@ -430,27 +442,28 @@ impl MachineInstance { fn get_logs_rollups( path: &str, + start_input: u64, agree_hash: Digest, meta_cycle: U256, - db: &DisputeStateAccess, + db: &EpochData, ) -> Result<(Vec, Digest)> { - let input_mask = (U256::one() << LOG2_UARCH_SPAN_TO_INPUT) - U256::one(); + let input_mask = (U256::ONE << LOG2_UARCH_SPAN_TO_INPUT) - U256::ONE; let big_step_mask = UARCH_SPAN_TO_BARCH; assert!(((meta_cycle >> LOG2_UARCH_SPAN_TO_INPUT) & !input_mask).is_zero()); - let meta_cycle_u128 = meta_cycle - .to_u128() - .expect("meta_cycle is too large to fit in u128"); + let meta_cycle_u128 = + u128::try_from(meta_cycle).expect("meta_cycle is too large to fit in u128"); let input_count = (meta_cycle_u128 >> LOG2_UARCH_SPAN_TO_INPUT) as u64; let mut logs = Vec::new(); - let mut machine = MachineInstance::new_rollups_advanced_until(path, meta_cycle, db)?; + let mut machine = + MachineInstance::new_rollups_resumed_until(path, start_input, meta_cycle, db)?; assert_eq!(machine.state()?.root_hash, agree_hash); if (meta_cycle & input_mask).is_zero() { - let input = db.input(input_count)?; + let input = db.input(input_count); let mut da_proof; let cmio_log; @@ -496,15 +509,18 @@ impl MachineInstance { } } + /// `path` and `start_input` come from the snapshot source: the + /// nearest input-boundary machine at or before the disputed cycle. pub fn get_logs( path: &str, + start_input: u64, agree_hash: Digest, meta_cycle: U256, - db: &DisputeStateAccess, + db: &EpochData, ) -> Result<(Vec, Digest)> { let (proofs, next_hash); - let result = Self::get_logs_rollups(path, agree_hash, meta_cycle, db)?; + let result = Self::get_logs_rollups(path, start_input, agree_hash, meta_cycle, db)?; proofs = result.0; next_hash = result.1; diff --git a/prt/client-rs/core/src/machine/error.rs b/cartesi-rollups/node/tests/common/machine_error.rs similarity index 76% rename from prt/client-rs/core/src/machine/error.rs rename to cartesi-rollups/node/tests/common/machine_error.rs index 60bc835d5..1960e42c7 100644 --- a/prt/client-rs/core/src/machine/error.rs +++ b/cartesi-rollups/node/tests/common/machine_error.rs @@ -1,6 +1,5 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -use crate::db::sql::error::DisputeStateAccessError; use cartesi_machine::error::MachineError; use thiserror::Error; @@ -12,12 +11,6 @@ pub enum MachineInstanceError { source: MachineError, }, - #[error(transparent)] - DisputeStateAccessError { - #[from] - source: DisputeStateAccessError, - }, - #[error("Invalid hex string")] InvalidHexString(#[from] hex::FromHexError), diff --git a/cartesi-rollups/node/tests/common/mod.rs b/cartesi-rollups/node/tests/common/mod.rs new file mode 100644 index 000000000..8e79cb9d3 --- /dev/null +++ b/cartesi-rollups/node/tests/common/mod.rs @@ -0,0 +1,13 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Shared scaffolding for the integration tests. + +// The prototype dialect (positioning by shift/mask, its own snapshot +// bookkeeping): deliberately independent of the engine ruler, which is +// exactly what makes it a differential oracle. Retired from +// production by workstream 4; lives on here. +pub mod epoch_data; +pub mod instance; +pub mod machine_error; +pub mod prototype; diff --git a/prt/client-rs/core/src/machine/commitment.rs b/cartesi-rollups/node/tests/common/prototype.rs similarity index 50% rename from prt/client-rs/core/src/machine/commitment.rs rename to cartesi-rollups/node/tests/common/prototype.rs index 3b2b4cc4e..7cce1c671 100644 --- a/prt/client-rs/core/src/machine/commitment.rs +++ b/cartesi-rollups/node/tests/common/prototype.rs @@ -1,29 +1,175 @@ -//! This module defines a struct [MachineCommitment] that is used to represent a `computation hash` -//! described on the paper https://arxiv.org/pdf/2212.12439.pdf. +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The prototype commitment builder: the differential oracle the engine +//! machine tests compare against (see docs/plans/sling-design.md). It +//! predates the quartet cache and computes commitments by driving the +//! machine leaf by leaf; nothing here persists - leaf runs are cached +//! in memory per builder, purely to mirror the old behavior. + +use super::epoch_data::{EpochData, Leaf}; +use super::instance::MachineInstance; +use super::machine_error::Result; +use cartesi_rollups_prt_node::engine::constants; use alloy::primitives::U256; +use cartesi_rollups_prt_node::arithmetic::max_uint; +use cartesi_rollups_prt_node::merkle::{Digest, MerkleBuilder, MerkleTree}; use log::{info, trace}; +use std::collections::HashMap; use std::io::{self, Write}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Instant; -use crate::{ - db::dispute_state_access::{DisputeStateAccess, Leaf}, - machine::error::Result, - machine::{MachineInstance, constants}, -}; -use cartesi_dave_arithmetic::max_uint; -use cartesi_dave_merkle::{Digest, MerkleBuilder, MerkleTree}; +/// The oracle's leaf-run cache, keyed by (level, base_cycle); appended +/// run by run, read back whole. +#[derive(Debug, Default)] +pub struct LeafStore { + leafs: Mutex>>, +} + +impl LeafStore { + pub fn insert_leafs<'a>( + &self, + level: u64, + base_cycle: U256, + leafs: impl Iterator, + ) { + let mut map = self.leafs.lock().unwrap(); + map.entry((level, base_cycle)) + .or_default() + .extend(leafs.cloned()); + } + + pub fn leafs( + &self, + level: u64, + log2_stride: u64, + log2_stride_count: u64, + base_cycle: U256, + ) -> Vec<(Arc, u64)> { + let leafs: Vec = self + .leafs + .lock() + .unwrap() + .get(&(level, base_cycle)) + .cloned() + .unwrap_or_default(); + + if log2_stride == 0 && !leafs.is_empty() { + leafs_with_uarch(leafs, log2_stride_count) + } else { + leafs + .iter() + .map(|leaf| (digest_tree(&leaf.hash), leaf.repetitions)) + .collect() + } + } +} + +fn digest_tree(hash: &[u8; 32]) -> Arc { + Digest::from_digest(hash) + .expect("leaf hashes are 32 bytes") + .into() +} + +fn leafs_with_uarch(leafs: Vec, log2_stride_count: u64) -> Vec<(Arc, u64)> { + let mut main_tree = Vec::new(); + let span_count = max_uint(log2_stride_count - constants::LOG2_UARCH_SPAN_TO_BARCH) + 1; + let span_size = constants::UARCH_SPAN_TO_BARCH + 1; + let mut accumulated_repetitions = 0; + let mut uarch_tree_builder = MerkleBuilder::default(); + + for leaf in leafs { + if accumulated_repetitions == 0 { + // reset the uarch_tree builder + uarch_tree_builder = MerkleBuilder::default(); + } + + if accumulated_repetitions < span_size { + uarch_tree_builder.append_repeated( + Digest::from_digest(&leaf.hash).expect("leaf hashes are 32 bytes"), + leaf.repetitions, + ); + accumulated_repetitions += leaf.repetitions; + } + if accumulated_repetitions == span_size { + // here we build a uarch_tree and add it to the main tree + main_tree.push((uarch_tree_builder.build(), 1)); + // reset the accumulated repetitions + accumulated_repetitions = 0; + } + } + + assert!(!main_tree.is_empty()); + let main_tree_len = main_tree.len() as u64; + if main_tree_len < span_count { + main_tree.push((uarch_tree_builder.build(), span_count - main_tree_len)); + } + + main_tree +} -/// The [MachineCommitment] struct represents a `computation hash`, that is a [MerkleTree] of a set -/// of steps of the Cartesi Machine. +/// A `computation hash`: a merkle tree over a set of machine steps. #[derive(Clone, Debug)] pub struct MachineCommitment { + #[allow(dead_code)] pub implicit_hash: Digest, pub merkle: Arc, } -/// Builds a [MachineCommitment] from a [MachineInstance] and a base cycle and leafs. +pub struct MachineCommitmentBuilder { + machine_path: String, + leafs: LeafStore, +} + +impl MachineCommitmentBuilder { + pub fn new(machine_path: String) -> Self { + MachineCommitmentBuilder { + machine_path, + leafs: LeafStore::default(), + } + } + + pub fn build_commitment( + &mut self, + base_cycle: U256, + level: u64, + log2_stride: u64, + log2_stride_count: u64, + db: &EpochData, + ) -> Result { + let mut machine = + MachineInstance::new_rollups_advanced_until(&self.machine_path, base_cycle, db)?; + let initial_state = machine.root_hash()?; + + trace!("initial state for commitment: {}", initial_state); + let commitment = { + let mut leafs = self + .leafs + .leafs(level, log2_stride, log2_stride_count, base_cycle); + // leafs are cached, use them to calculate merkle + if leafs.is_empty() { + // leafs are not cached, build merkle by running the machine + leafs = build_machine_commitment( + &mut machine, + base_cycle, + level, + log2_stride, + log2_stride_count, + db, + &self.leafs, + )?; + assert!(!leafs.is_empty()); + } + build_machine_commitment_from_leafs(leafs, initial_state)? + }; + + Ok(commitment) + } +} + +/// Builds a [MachineCommitment] from leafs. pub fn build_machine_commitment_from_leafs( leafs: Vec<(L, u64)>, initial_state: Digest, @@ -44,13 +190,15 @@ where } /// Builds a [MachineCommitment] from a [MachineInstance] and a base cycle. +#[allow(clippy::too_many_arguments)] pub fn build_machine_commitment( machine: &mut MachineInstance, base_cycle: U256, level: u64, log2_stride: u64, log2_stride_count: u64, - db: &DisputeStateAccess, + db: &EpochData, + store: &LeafStore, ) -> Result, u64)>> { info!( "Begin building commitment for level {level}: start cycle {base_cycle}, log2_stride {log2_stride} and log2_stride_count {log2_stride_count}" @@ -79,11 +227,11 @@ pub fn build_machine_commitment( base_cycle, log2_stride, log2_stride_count, - db, + store, )?; } else { assert!(log2_stride == 0); - build_small_machine_commitment(machine, level, base_cycle, log2_stride_count, db)?; + build_small_machine_commitment(machine, level, base_cycle, log2_stride_count, store)?; } info!( @@ -91,7 +239,7 @@ pub fn build_machine_commitment( start.elapsed().as_secs() ); - Ok(db.leafs(level, log2_stride, log2_stride_count, base_cycle)?) + Ok(store.leafs(level, log2_stride, log2_stride_count, base_cycle)) } /// Builds a [MachineCommitment] Hash for the Cartesi Machine using the big machine model. @@ -101,7 +249,7 @@ fn build_big_machine_commitment( base_cycle: U256, log2_stride: u64, log2_stride_count: u64, - db: &DisputeStateAccess, + store: &LeafStore, ) -> Result<()> { let mut leafs = Vec::new(); let instruction_count = 1 << log2_stride_count; @@ -132,7 +280,7 @@ fn build_big_machine_commitment( } finish_print_flush_same_line(); - db.insert_leafs(level, base_cycle, leafs.iter())?; + store.insert_leafs(level, base_cycle, leafs.iter()); Ok(()) } @@ -142,7 +290,7 @@ fn build_small_machine_commitment( level: u64, base_cycle: U256, log2_stride_count: u64, - db: &DisputeStateAccess, + store: &LeafStore, ) -> Result<()> { let span_count = max_uint(log2_stride_count - constants::LOG2_UARCH_SPAN_TO_BARCH); @@ -153,14 +301,14 @@ fn build_small_machine_commitment( span, span_count )); - run_uarch_span(machine, base_cycle, level, db)?; + run_uarch_span(machine, base_cycle, level, store)?; let machine_state = machine.state()?; span += 1; // if the machine is yielded, we need to run another uarch span if machine_state.halted || machine_state.yielded { trace!("uarch span machine halted/yielded"); - run_uarch_span(machine, base_cycle, level, db)?; + run_uarch_span(machine, base_cycle, level, store)?; break; } } @@ -173,7 +321,7 @@ fn run_uarch_span( machine: &mut MachineInstance, base_cycle: U256, level: u64, - db: &DisputeStateAccess, + store: &LeafStore, ) -> Result<()> { let (_, ucycle) = machine.position()?; assert!(ucycle == 0); @@ -215,7 +363,7 @@ fn run_uarch_span( hash: machine.root_hash()?.into(), repetitions: 1, }); - db.insert_leafs(level, base_cycle, leafs.iter())?; + store.insert_leafs(level, base_cycle, leafs.iter()); Ok(()) } diff --git a/cartesi-rollups/node/tests/engine_machine.rs b/cartesi-rollups/node/tests/engine_machine.rs new file mode 100644 index 000000000..8465cf739 --- /dev/null +++ b/cartesi-rollups/node/tests/engine_machine.rs @@ -0,0 +1,476 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Increment B differentials: the engine reference collector against the +//! prototype's commitment builder, on the real echo machine, plus golden +//! fixtures pinning the roots. +//! +//! These tests need the echo machine image (built by `just setup-local`) +//! and skip with a notice when it is absent. The fixture file records +//! the template hash: an emulator or image bump invalidates it loudly, +//! and regeneration (UPDATE_FIXTURES=1) is a conscious, reviewable act. + +use alloy::primitives::{Address, U256}; +use alloy::sol_types::SolCall; +mod common; +use common::prototype::{MachineCommitment, MachineCommitmentBuilder}; + +use cartesi_rollups_prt_node::engine::{ + DisputeSource, LevelCoords, MachineStf, Positioner, Quartet, Stf, Structure, +}; +use cartesi_rollups_prt_node::storage::{Input as StorageInput, InputId, Storage}; +use common::epoch_data::EpochData; +use common::instance::MachineInstance; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +fn echo_image() -> Option { + let path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../test/programs/echo/machine-image"); + path.exists().then(|| path.canonicalize().unwrap()) +} + +fn fixture_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/engine_echo.json") +} + +// The canonical input encoding: what InputBox.addInput wraps payloads +// into and what the machine's rollup driver decodes. Signature from +// cartesi-rollups-contracts (Inputs.sol); raw bytes would crash the +// driver and halt the machine. +alloy::sol! { + function EvmAdvance( + uint256 chainId, + address appContract, + address msgSender, + uint256 blockNumber, + uint256 blockTimestamp, + uint256 prevRandao, + uint256 index, + bytes memory payload + ) external; +} + +fn echo_inputs() -> Vec> { + [&b"hello dave"[..], &b"hello again, dave"[..]] + .iter() + .enumerate() + .map(|(index, payload)| { + EvmAdvanceCall { + chainId: U256::from(31337), + appContract: Address::ZERO, + msgSender: Address::ZERO, + blockNumber: U256::from(1), + blockTimestamp: U256::from(1), + prevRandao: U256::from(0), + index: U256::from(index), + payload: payload.to_vec().into(), + } + .abi_encode() + }) + .collect() +} + +/// Test scratch: under target/tmp (visible, swept by cargo clean) and +/// cleaned on drop. Never .keep() into the system TMPDIR - nothing +/// sweeps it, and these dirs carry machine stores (806 GB of orphans +/// found there, 2026-07-11). Callers hold the guard for as long as +/// the path is in use. +fn scratch() -> tempfile::TempDir { + tempfile::tempdir_in(env!("CARGO_TARGET_TMPDIR")).unwrap() +} + +fn template_hash(image: &Path) -> String { + let work = scratch(); + let mut stf = MachineStf::load(image, work.path().to_path_buf()).unwrap(); + stf.state_hash().unwrap().to_hex() +} + +/// The prototype's answer for a span: its commitment builder, backed +/// by in-memory epoch data. +fn prototype_root(image: &Path, level: u64, log2_stride: u64, log2_stride_count: u64) -> String { + let dir = scratch(); + let db = EpochData::new(echo_inputs(), dir.path().to_path_buf()).unwrap(); + let mut builder = MachineCommitmentBuilder::new(image.to_str().unwrap().into()); + let commitment = builder + .build_commitment(U256::ZERO, level, log2_stride, log2_stride_count, &db) + .unwrap(); + commitment.merkle.root_hash().to_hex() +} + +/// A real migrated node database in a temp state dir, the echo +/// inputs ingested through the production path (payloads live in the +/// inputs table; feeders read them there). The guard rides along: +/// the state dir must outlive the Storage. +fn migrated_storage(image: &Path) -> (tempfile::TempDir, Storage) { + let dir = scratch(); + let mut storage = Storage::migrate(dir.path(), image, 0, Address::ZERO).unwrap(); + let rows: Vec = echo_inputs() + .into_iter() + .enumerate() + .map(|(index, data)| StorageInput { + id: InputId { + epoch_number: 0, + input_index_in_epoch: index as u64, + }, + data, + }) + .collect(); + storage + .insert_consensus_data(0, rows.iter(), std::iter::empty()) + .unwrap(); + (dir, storage) +} + +/// The engine answer for the same span, through the facade. +fn engine_root(image: &Path, log2_stride: u64, height: u64) -> String { + let (guards, mut source) = machine_source(image); + let quartet = Quartet::level_root(0, log2_stride, height); + let root = source.node(&quartet).unwrap().to_hex(); + drop(guards); + root +} + +/// Engine vs prototype on identical spans, at three granularities: the +/// uarch span of the fused first big cycle, a mid-stride span, and a +/// coarse span that crosses the first input's yield into padding. +#[test] +fn reference_collector_matches_prototype() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + // (label, log2_stride, height): spans all start at position 0 and + // fit inside window 0, which is all the prototype's machine-backed + // builder supports (deeper levels never cross windows). + let spans = [ + ("uarch_span_r0_h20", 0u64, 20u64), + ("mid_stride_r27_h10", 27, 10), + ("coarse_r44_h4", 44, 4), + ]; + + for (index, (label, log2_stride, height)) in spans.into_iter().enumerate() { + let prototype = prototype_root(&image, index as u64, log2_stride, height); + let engine = engine_root(&image, log2_stride, height); + assert_eq!( + engine, prototype, + "engine and prototype disagree on {label}" + ); + println!("{label}: {engine}"); + } +} + +/// The prototype's whole in-memory commitment for a span. +fn prototype_commitment( + image: &Path, + level: u64, + base_cycle: U256, + log2_stride: u64, + log2_stride_count: u64, +) -> MachineCommitment { + let dir = scratch(); + let db = EpochData::new(echo_inputs(), dir.path().to_path_buf()).unwrap(); + let mut builder = MachineCommitmentBuilder::new(image.to_str().unwrap().into()); + builder + .build_commitment(base_cycle, level, log2_stride, log2_stride_count, &db) + .unwrap() +} + +/// A dispute source over a freshly migrated state dir: the epoch +/// start is the only stored boundary, i.e. the template-replay +/// behavior - until its own positioning densifies the store. +fn machine_source(image: &Path) -> (Vec, DisputeSource) { + let (state_dir, storage) = migrated_storage(image); + let work = scratch(); + let source = DisputeSource::on_store(storage, 0, work.path().to_path_buf()).unwrap(); + (vec![state_dir, work], source) +} + +/// The increment-C differential: every query shape the Player sends +/// during a dispute (roots, bisection children, seal and join proofs) +/// against the prototype's in-memory tree, on the real machine. Two +/// levels: a mid-stride level at the epoch start, and a uarch-stride +/// level inside window 1, which crosses an input feed during replay. +#[test] +fn dispute_source_matches_prototype_tree() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let spans = [ + ("mid_stride_r27_h10", U256::ZERO, 27u64, 10u64), + ("window1_uarch_r0_h20", U256::from(1) << 68, 0, 20), + // The shape that lost the first e2e dispute: a uarch-stride + // level over pure idle padding (echo yielded long before + // 2^44), where the leaf material is the idle churn pattern. + ("idle_padding_r0_h28", U256::from(1) << 44, 0, 28), + ]; + + for (index, (label, base, log2_stride, height)) in spans.into_iter().enumerate() { + let prototype = prototype_commitment(&image, index as u64, base, log2_stride, height); + let (_scratch, mut source) = machine_source(&image); + let level = LevelCoords::new(0, base, log2_stride, height); + + let root = source.node(&level.root()).unwrap(); + assert_eq!(root, prototype.merkle.root_hash(), "{label}: root"); + + let (left, right) = source.children(&level.root()).unwrap(); + let (pl, pr) = prototype.merkle.subtrees().unwrap(); + assert_eq!( + (left, right), + (pl.root_hash(), pr.root_hash()), + "{label}: root children" + ); + + // Proof descents at the shapes the strategy sends: the join's + // last-leaf proof and a mid-tree agree proof. Indices cross + // fanout strata (heights 10 and 20 both exceed one stratum). + let last = source.prove_last(&level).unwrap(); + let expected_last = prototype.merkle.prove_last(); + assert_eq!(last.node, expected_last.node, "{label}: last leaf"); + assert_eq!(last.siblings, expected_last.siblings, "{label}: last proof"); + + let mid = (U256::from(1) << height) / U256::from(2) - U256::from(1); + let agree = source.prove_leaf(&level, mid).unwrap(); + let expected_agree = prototype.merkle.prove_leaf(mid); + assert_eq!(agree.node, expected_agree.node, "{label}: agree leaf"); + assert_eq!( + agree.siblings, expected_agree.siblings, + "{label}: agree proof" + ); + + println!("{label}: {}", root.to_hex()); + } +} + +/// The increment-D differential: a source answering with a mid-epoch +/// snapshot must produce the same tree material as the template +/// replay, on the query shapes the Player sends. The snapshot is the +/// window-1 boundary, produced the production way: a write-back +/// ruler crosses it and commits it into the store the resumed +/// source reads. +#[test] +fn snapshot_resumed_source_matches_template_replay() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let (_replayed_scratch, mut replayed) = machine_source(&image); + let (resumed_scratch, mut resumed) = machine_source(&image); + + // Cross boundary 1 with a write-back stf: feed(input 0) commits + // boundary 0 (absorbed - the epoch start), feed(input 1) commits + // boundary 1. The post-feed machine is discarded scratch. + { + let work = scratch(); + let stf = MachineStf::load(&image, work.path().to_path_buf()).unwrap(); + let mut stf = stf.with_write_back(Storage::new(resumed_scratch[0].path()).unwrap(), 0, 0); + stf.feed(0).unwrap(); + while stf.run_big(u64::MAX).unwrap() > 0 {} + assert!(stf.yielded().unwrap()); + stf.feed(1).unwrap(); + } + let mut check = Storage::new(resumed_scratch[0].path()).unwrap(); + assert_eq!(check.nearest_boundary_at_or_before(0, 1).unwrap().0.0, 1); + + let level = LevelCoords::new(0, U256::from(1) << 68, 0, 20); + + assert_eq!( + resumed.node(&level.root()).unwrap(), + replayed.node(&level.root()).unwrap(), + "root" + ); + assert_eq!( + resumed.children(&level.root()).unwrap(), + replayed.children(&level.root()).unwrap(), + "children" + ); + let (a, b) = ( + resumed.prove_last(&level).unwrap(), + replayed.prove_last(&level).unwrap(), + ); + assert_eq!(a.node, b.node, "last leaf"); + assert_eq!(a.siblings, b.siblings, "last proof"); + let mid = U256::from(1) << 10; + let (a, b) = ( + resumed.prove_leaf(&level, mid).unwrap(), + replayed.prove_leaf(&level, mid).unwrap(), + ); + assert_eq!(a.node, b.node, "mid leaf"); + assert_eq!(a.siblings, b.siblings, "mid proof"); +} + +/// Step-5 write-back: positioning that crosses a window boundary +/// commits it into the boundary store, so the next ruler resumes at +/// most one window away - and a boundary regime 1 already recorded +/// absorbs identically (the cross-regime tripwire staying silent on +/// agreement). +#[test] +fn positioning_writes_back_crossed_boundaries() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let (guards, mut source) = machine_source(&image); + let mut storage = Storage::new(guards[0].path()).unwrap(); + + // A fresh store has only the epoch start. + let (floor, _) = storage.nearest_boundary_at_or_before(0, 1).unwrap(); + assert_eq!(floor.0, 0); + + // A window-1 quartet: positioning replays across boundary 1. + let level = LevelCoords::new(0, U256::from(1) << 68, 0, 20); + source.node(&level.root()).unwrap(); + + // The replay fed input 0, so boundary 1 is now stored and the + // next positioning starts there. + let (boundary, path) = storage.nearest_boundary_at_or_before(0, 1).unwrap(); + assert_eq!(boundary.0, 1); + assert!(path.join("config.json").exists()); + assert!(storage.snapshot_hash(0, 1).unwrap().is_some()); +} + +/// The emulator semantics the ruler's idle replay relies on, pinned +/// executably: stepping the uarch of a yielded machine is not an +/// identity (the emulated interpreter churns its own bookkeeping), the +/// churn sequence is identical on every idle span, and the closing +/// ureset restores the base hash exactly. If an emulator bump breaks +/// any of these, idle regions can no longer be replayed from one +/// stepped span and the convention itself must be revisited. +#[test] +fn idle_spans_are_periodic_and_ureset_restores_the_base() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let work = scratch(); + let mut stf = MachineStf::load(&image, work.path().to_path_buf()) + .unwrap() + .with_inputs(echo_inputs()); + stf.feed(0).unwrap(); + while stf.run_big(u64::MAX).unwrap() > 0 {} + assert!(stf.yielded().unwrap()); + + let base = stf.state_hash().unwrap(); + let mut spans = vec![]; + for _ in 0..2 { + let mut hashes = vec![]; + while !stf.uarch_halted().unwrap() { + stf.ustep().unwrap(); + hashes.push(stf.state_hash().unwrap()); + } + stf.ureset().unwrap(); + assert_eq!( + stf.state_hash().unwrap(), + base, + "idle ureset must restore the base state" + ); + spans.push(hashes); + } + assert!(!spans[0].is_empty(), "idle churn must be observable"); + assert_ne!(spans[0][0], base, "idle usteps are not identities"); + assert_eq!(spans[0], spans[1], "idle spans must be periodic"); +} + +/// The full-epoch level-0 commitment shape has no in-crate prototype +/// comparator (the prototype gets those leaves from the node); pin it +/// as a golden fixture instead, along with the differential roots. +#[test] +fn golden_fixtures_hold() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let mut computed = BTreeMap::new(); + computed.insert("template_hash".to_string(), template_hash(&image)); + computed.insert( + "epoch_root_r44_h48".to_string(), + engine_root(&image, 44, 48), + ); + computed.insert("uarch_span_r0_h20".to_string(), engine_root(&image, 0, 20)); + computed.insert( + "mid_stride_r27_h10".to_string(), + engine_root(&image, 27, 10), + ); + + let path = fixture_path(); + if std::env::var("UPDATE_FIXTURES").is_ok() { + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(&path, serde_json::to_string_pretty(&computed).unwrap()).unwrap(); + println!("fixtures written to {}", path.display()); + return; + } + + let stored: BTreeMap = + serde_json::from_str(&std::fs::read_to_string(&path).unwrap_or_else(|_| { + panic!( + "fixture file missing: {}; generate it with UPDATE_FIXTURES=1 \ + and commit it after review", + path.display() + ) + })) + .unwrap(); + + assert_eq!( + computed, stored, + "golden fixtures diverged; if the emulator or the echo image \ + changed intentionally, regenerate with UPDATE_FIXTURES=1" + ); +} + +/// The workstream-4 differential: the ruler-guided proof path +/// (DisputeSource::machine_at + Ruler::prove_transition) must produce +/// byte-identical chain witnesses to the prototype's get_logs, across +/// the transition shapes reachable on the echo epoch: the fed window +/// start, a plain ustep, a closing slot, and an inputless window +/// start (empty data availability). The revert-carrying closing slot +/// is pinned on-chain by the stf_revert e2e scenario. +#[test] +fn prove_transition_matches_prototype_get_logs() { + let Some(image) = echo_image() else { + eprintln!("skipping: echo machine image not built (just setup-local)"); + return; + }; + + let structure = Structure::PRODUCTION; + let inputs = echo_inputs(); + + let shapes = [ + ("fed_window_start", U256::ZERO), + ("plain_ustep", U256::from(1)), + ("closing_slot", U256::from(structure.big_span() - 1)), + ("inputless_window_start", structure.window_start(2)), + ]; + + for (label, meta_cycle) in shapes { + // Both sides position independently from the template. + // The state dir is a guard: storage lives inside it. + let (_state_dir, storage) = migrated_storage(&image); + let work = scratch(); + let mut source = DisputeSource::on_store(storage, 0, work.path().to_path_buf()).unwrap(); + let mut ruler = source.machine_at(meta_cycle).unwrap(); + let agree = ruler.state_hash().unwrap(); + let (new_proof, new_next) = ruler.prove_transition().unwrap(); + + let dir = scratch(); + let db = EpochData::new(inputs.clone(), dir.path().to_path_buf()).unwrap(); + let (old_proof, old_next) = + MachineInstance::get_logs(image.to_str().unwrap(), 0, agree, meta_cycle, &db).unwrap(); + + assert_eq!( + new_proof, old_proof, + "proof bytes diverge at {label} (position {meta_cycle})" + ); + assert_eq!( + new_next, old_next, + "post-transition hash diverges at {label} (position {meta_cycle})" + ); + println!("{label}: {} witness bytes agree", new_proof.len()); + } +} diff --git a/cartesi-rollups/node/tests/fixtures/chain-recordings/README.md b/cartesi-rollups/node/tests/fixtures/chain-recordings/README.md new file mode 100644 index 000000000..9f21b6fe8 --- /dev/null +++ b/cartesi-rollups/node/tests/fixtures/chain-recordings/README.md @@ -0,0 +1,31 @@ +# Chain recordings + +Raw devnet log ranges captured after e2e disputes settle: every log +the chain emitted (unfiltered, undecoded) plus the timestamp of each +block carrying one. They are the oracle material for the +tournament-state fold (docs/plans/node-refactor.md, workstream 5): +fold tests decode these through the same bindings the production +fetcher uses, so the fixtures cannot bake in decoding assumptions. + +Regenerate from prt/tests/rollups (the node and record_chain binaries +must be built): + +``` +RECORD_CHAIN_FIXTURE=/echo_simple.json \ + just test-echo + +RECORD_CHAIN_FIXTURE=/multilevel_stf.json \ + just test-honeypot-stf + +RECORD_CHAIN_FIXTURE=/multi_sybil.json \ + just test-multi-sybil +``` + +echo_simple is one dispute descending all three levels; multilevel_stf +is the state-transition suite's five epochs, one steered dispute per +on-chain transition shape. + +The hook lives in test_env.lua's run_epoch: it records after each +epoch settles, so a multi-epoch scenario's final recording contains +the whole run. Committing a regenerated fixture is a reviewed act, +like every fixture in this repo. diff --git a/cartesi-rollups/node/tests/fixtures/chain-recordings/echo_simple.json b/cartesi-rollups/node/tests/fixtures/chain-recordings/echo_simple.json new file mode 100644 index 000000000..538cfb2f1 --- /dev/null +++ b/cartesi-rollups/node/tests/fixtures/chain-recordings/echo_simple.json @@ -0,0 +1,3572 @@ +{ + "note": "epoch-1", + "chain_id": 31337, + "from_block": 0, + "to_block": 838, + "block_timestamps": { + "23": 1784543707, + "24": 1784543707, + "27": 1784543707, + "28": 1784543707, + "29": 1784543707, + "32": 1784543709, + "33": 1784543709, + "419": 1784543720, + "420": 1784543720, + "423": 1784543731, + "424": 1784543731, + "429": 1784543736, + "431": 1784543736, + "432": 1784543737, + "434": 1784543737, + "435": 1784543739, + "437": 1784543739, + "438": 1784543740, + "440": 1784543740, + "441": 1784543741, + "443": 1784543741, + "444": 1784543742, + "446": 1784543743, + "447": 1784543743, + "449": 1784543744, + "450": 1784543745, + "452": 1784543746, + "453": 1784543746, + "455": 1784543747, + "456": 1784543747, + "458": 1784543748, + "459": 1784543748, + "461": 1784543749, + "462": 1784543750, + "464": 1784543751, + "465": 1784543751, + "467": 1784543752, + "468": 1784543752, + "470": 1784543753, + "471": 1784543753, + "473": 1784543754, + "474": 1784543755, + "476": 1784543756, + "477": 1784543756, + "479": 1784543757, + "480": 1784543757, + "482": 1784543758, + "483": 1784543759, + "485": 1784543759, + "486": 1784543760, + "488": 1784543761, + "489": 1784543761, + "491": 1784543762, + "492": 1784543762, + "494": 1784543763, + "495": 1784543764, + "497": 1784543764, + "498": 1784543765, + "500": 1784543766, + "501": 1784543766, + "503": 1784543767, + "504": 1784543768, + "506": 1784543768, + "507": 1784543770, + "509": 1784543770, + "510": 1784543771, + "512": 1784543771, + "513": 1784543773, + "515": 1784543773, + "516": 1784543774, + "518": 1784543774, + "519": 1784543776, + "521": 1784543776, + "522": 1784543777, + "524": 1784543777, + "525": 1784543779, + "527": 1784543779, + "528": 1784543780, + "530": 1784543780, + "531": 1784543782, + "533": 1784543782, + "534": 1784543783, + "536": 1784543784, + "537": 1784543785, + "539": 1784543785, + "540": 1784543787, + "542": 1784543787, + "543": 1784543788, + "545": 1784543789, + "546": 1784543790, + "548": 1784543790, + "549": 1784543791, + "551": 1784543792, + "552": 1784543793, + "554": 1784543793, + "555": 1784543795, + "557": 1784543795, + "558": 1784543796, + "560": 1784543797, + "561": 1784543798, + "563": 1784543798, + "564": 1784543800, + "566": 1784543800, + "567": 1784543801, + "569": 1784543802, + "570": 1784543803, + "572": 1784543803, + "573": 1784543805, + "575": 1784543805, + "576": 1784543806, + "577": 1784543806, + "578": 1784543806, + "835": 1784543811, + "836": 1784543812, + "837": 1784543813, + "838": 1784543813 + }, + "logs": [ + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def" + ], + "data": "0x", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xc549f89cf1ca43eddecc64ac2208f4b283b1c483", + "topics": [ + "0xf57fedb261f4593784de9abb6653acfbaf45e74182818717c6e9b39c344a2a78", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22defe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea0000000000000000000000000000000000000000000000000000000000000024b12c9ede000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad14393900000000000000000000000000000000000000000000000000000000", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xaf68463e16cb5595a44214bea8d366ecf7cd3410269c50f92c104b50a7829daa" + ], + "data": "0x000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad143939000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea00000000000000000000000064f6cf454348f891e837eddc99be67ea98c64602", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x6ad3188ba8f430fba0656cb0a7e839ab2020d5586ba11a1477d18f7092f8bece" + ], + "data": "0x0000000000000000000000009da58d313a19d0185ff8ad3bad7e538d9e477697", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x5", + "removed": false + }, + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x6", + "removed": false + }, + { + "address": "0x0fdab499db8a58f8d04dc6ad2b05e72252b22def", + "topics": [ + "0xdf2ebeb5a7d7df0100c0274c7cee9570954d7bebeef37db55b27204a57f65602" + ], + "data": "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea0000000000000000000000009da58d313a19d0185ff8ad3bad7e538d9e477697", + "blockHash": "0x82d14f064f2ab16a74faebd4c0534790bfcbe6cb392117a284829118f1c78eb3", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x7", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000006a5df9db7ebb0c6e6ecefaf8778dfd34c470bb63b7bfffea06d12d8c526fbbaa944e973800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc752f74f980932bbadec390146ce18df3eb2a58f921e2b8b2913d8ee2c50e613", + "blockNumber": "0x18", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x5c1823cadd54a9e08be021d7272103d8af0b90aaefd00f625d797854708bbd58", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000006a5df9db963300ff1287355ffd6b94557dd4aa9fabf33891db0b9e39479d660e8ed58a3c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb7524a2b7f6d1a51679e9197b3b656d6ee04e4e82b9a15cca9b0c493df5e8a62", + "blockNumber": "0x1b", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0x92cd6ff7fb1243cc4d1098c6f713f6c7392eb9135b5b37d8c34fbfe2ac4a5bca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000006a5df9dbb24da455afa72f7c412b403da46e34ede8e347b0f40ac4ef991090289389dbe600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdc2f48a7f4cc22abf4a48e554ffc33f6712f44d0eeb32f6004d5f64353d2bba2", + "blockNumber": "0x1c", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0xe571e4686ae563533b0cef1c7192e78cc98d03ef1bc0c205e0b728b0e853beb8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000006a5df9db44c748cd97b931aa18ec8e09924f334975fef934e3e4a34b04194144830b15ec00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x39a25499c955eebf69d4292a8b2cce2a1b78c86b66b924056cd98def6438a965", + "blockNumber": "0x1d", + "blockTimestamp": "0x6a5df9db", + "transactionHash": "0xbb51539f611933a63f52ad4573679e7575c08f12d1fda2926c5e7d0f91fa0710", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0xe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821", + "blockHash": "0x638998ae1a32513337829185a62e84c009ecb13a54dc5f3e39569ad92d1974f3", + "blockNumber": "0x20", + "blockTimestamp": "0x6a5df9dd", + "transactionHash": "0xd9be04fbd243243a21edb384b212a46d0d895e064a2c4d40aac1ef40df93ac3d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xe366c3ef306160a2770c6e55551c28436ede846c", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x96d3518810435589f48da25a87536bfaa68df54ec1c9bee3c869d893f0967ce1e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821", + "blockHash": "0x7b3224a59f97028b04eecdb5d353f3c007015ed24804f59639a4349246f96093", + "blockNumber": "0x21", + "blockTimestamp": "0x6a5df9dd", + "transactionHash": "0x2c584fa9757425b1cd0d48999045f430e1344c037b5073e3d5b795ed2f7b98f3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0xe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0x5849f84f449c4685d1123f91e311c9ad83007984118f3d29a8369a904d744b00", + "blockNumber": "0x1a3", + "blockTimestamp": "0x6a5df9e8", + "transactionHash": "0x48c80e6fd4bff97868831adb760abe7bae0fd2cc0e8f78a04ddef60db541f867", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x59b02b918a10a333150b530491c34da007ebec0e609ab7e79490727a3de00fd8", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5df9e8", + "transactionHash": "0x529769b00763fd8481f22fec5728eb20739223fb0cd9d1af869cab9d0cce6806", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x59b02b918a10a333150b530491c34da007ebec0e609ab7e79490727a3de00fd8", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5df9e8", + "transactionHash": "0x529769b00763fd8481f22fec5728eb20739223fb0cd9d1af869cab9d0cce6806", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0xdc5586a6f64c8096a858d2ba73f04ca65bccaab1e8f305f9427d15cbfe5975e1", + "blockHash": "0x4a8a6a3a6678517bad9b1d926fecf3910a15899eee6bf39d84232c0e9eec460a", + "blockNumber": "0x1a7", + "blockTimestamp": "0x6a5df9f3", + "transactionHash": "0xee4ee26676553c5adcb5176375a292d000091483285f217bc762d56be50b9174", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x9db03e952e42d657a4d42d91ce301508bd2bf9b9b7ad28fcb32f58d9417d3e6ddc5586a6f64c8096a858d2ba73f04ca65bccaab1e8f305f9427d15cbfe5975e1", + "blockHash": "0xe3e51c789912eaff9b99f6aab77cfb0f78605b6c82c1b6a4891dfa3601bbbc8c", + "blockNumber": "0x1a8", + "blockTimestamp": "0x6a5df9f3", + "transactionHash": "0x3d5aa7c730f8c2f59c58817e4a1505311f844eb98b9615c5db908f6b169237a3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xc45dc5629f224af2e1568f1243f302c6072473d8d866e8b8ace990324e34cd07dc5586a6f64c8096a858d2ba73f04ca65bccaab1e8f305f9427d15cbfe5975e1", + "blockHash": "0x0c49be56f244351dad16a98d86e316f85eb8aa2a7955d42a04702ced46923d47", + "blockNumber": "0x1ad", + "blockTimestamp": "0x6a5df9f8", + "transactionHash": "0xf25a307ff3bb3464082b753d019bac9a7d9220067b2209518784a334b7b886e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a", + "0x9db03e952e42d657a4d42d91ce301508bd2bf9b9b7ad28fcb32f58d9417d3e6d", + "0xc45dc5629f224af2e1568f1243f302c6072473d8d866e8b8ace990324e34cd07" + ], + "data": "0x8954b268f00503ecf647048aedda9d11c5c3964c88a6c1465c0b8eaafbf2f062", + "blockHash": "0x0c49be56f244351dad16a98d86e316f85eb8aa2a7955d42a04702ced46923d47", + "blockNumber": "0x1ad", + "blockTimestamp": "0x6a5df9f8", + "transactionHash": "0xf25a307ff3bb3464082b753d019bac9a7d9220067b2209518784a334b7b886e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x8954b268f00503ecf647048aedda9d11c5c3964c88a6c1465c0b8eaafbf2f062e45a750efe6f1671f53add8174c85b884ca3cf3a265b703fbe9de539daecc313", + "blockHash": "0x93104a278b43fb71d6696f2e5d4c45acf4b29afb9ac218f3c2ea0ecad63dbc0d", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5df9f8", + "transactionHash": "0x4b442f4907246143e302476c34a4441acbadcd9101ecf6726d95aec118ac4a7b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x93104a278b43fb71d6696f2e5d4c45acf4b29afb9ac218f3c2ea0ecad63dbc0d", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5df9f8", + "transactionHash": "0x4b442f4907246143e302476c34a4441acbadcd9101ecf6726d95aec118ac4a7b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xe45a750efe6f1671f53add8174c85b884ca3cf3a265b703fbe9de539daecc3130181fb543fabd85a76fd0ee62a4e6229302f3ce5da212927a1fcd82908cb4769", + "blockHash": "0x379e5929ba64c707aa19df3f8969c23ef4db3a52d33d2eddfedd13607c5593f9", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5df9f9", + "transactionHash": "0xd9c6035126858d90e630f792ea4f1ef364e83c5a611ae519f54b65fbf020000f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x379e5929ba64c707aa19df3f8969c23ef4db3a52d33d2eddfedd13607c5593f9", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5df9f9", + "transactionHash": "0xd9c6035126858d90e630f792ea4f1ef364e83c5a611ae519f54b65fbf020000f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x0181fb543fabd85a76fd0ee62a4e6229302f3ce5da212927a1fcd82908cb4769d6f940d6489aed582fcd86d5c42221b3c05d0f3d99d0e6af3174f1b043a3ca03", + "blockHash": "0xb3354178c218b5f129c67f6d85c5870b4365270459dcdbcfb816129a3dca8a49", + "blockNumber": "0x1b2", + "blockTimestamp": "0x6a5df9f9", + "transactionHash": "0x982988d491f5aed22bde72e3c81d2d2d2ae4ca0c0be8abfa0381fb3169acc41a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb3354178c218b5f129c67f6d85c5870b4365270459dcdbcfb816129a3dca8a49", + "blockNumber": "0x1b2", + "blockTimestamp": "0x6a5df9f9", + "transactionHash": "0x982988d491f5aed22bde72e3c81d2d2d2ae4ca0c0be8abfa0381fb3169acc41a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xd6f940d6489aed582fcd86d5c42221b3c05d0f3d99d0e6af3174f1b043a3ca032648bdfde41df2b8f52bf0cb2b444d4497d9158a3cd6ecc4a14df11a6d154230", + "blockHash": "0x4db11922ec8d3ee2347592b1f8efee711390a3444febab5068e4879edbb642d6", + "blockNumber": "0x1b3", + "blockTimestamp": "0x6a5df9fb", + "transactionHash": "0x477077d161bce092c2eaa15f41cb918dc3ad179b2f3327705253ae0b437b5f71", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4db11922ec8d3ee2347592b1f8efee711390a3444febab5068e4879edbb642d6", + "blockNumber": "0x1b3", + "blockTimestamp": "0x6a5df9fb", + "transactionHash": "0x477077d161bce092c2eaa15f41cb918dc3ad179b2f3327705253ae0b437b5f71", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x2648bdfde41df2b8f52bf0cb2b444d4497d9158a3cd6ecc4a14df11a6d1542303b235b243594159f575a76c82b93e52904ce63afc248e91f564742953ca37931", + "blockHash": "0xac545c25b67be58756aa712d0e00f7b68dd34eaa8bde536b8abba623177fdadd", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5df9fb", + "transactionHash": "0x14b9dfdd59f18b57a67e53e39b4ffb19013a4a8b4490de5175ada2d5c5daeb19", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xac545c25b67be58756aa712d0e00f7b68dd34eaa8bde536b8abba623177fdadd", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5df9fb", + "transactionHash": "0x14b9dfdd59f18b57a67e53e39b4ffb19013a4a8b4490de5175ada2d5c5daeb19", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x3b235b243594159f575a76c82b93e52904ce63afc248e91f564742953ca379315b782fa508c01a3c678462159dc3a379bcfa6595a4128cd4a46909872acb3e28", + "blockHash": "0xe0f2bf8570aadf61838271f53a6e680a4c6bde592a1ed9ca6bf2b646fe142876", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5df9fc", + "transactionHash": "0x1d0a9be75a342b843c70c59eede38d445e4ddc6db144d6b166bc88a12e372337", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe0f2bf8570aadf61838271f53a6e680a4c6bde592a1ed9ca6bf2b646fe142876", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5df9fc", + "transactionHash": "0x1d0a9be75a342b843c70c59eede38d445e4ddc6db144d6b166bc88a12e372337", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x5b782fa508c01a3c678462159dc3a379bcfa6595a4128cd4a46909872acb3e289eb141226706573fc40ac654f064f438955d52d50a6b462b46672c53db4b399a", + "blockHash": "0xc85c63afa154dcc52f22280bb9f891813d115942b7c1da00791915b1017c0bba", + "blockNumber": "0x1b8", + "blockTimestamp": "0x6a5df9fc", + "transactionHash": "0xe9dcca67bad1bf3d2f20f99c96867760413b55f8a3577f0132efbe955c0919f5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc85c63afa154dcc52f22280bb9f891813d115942b7c1da00791915b1017c0bba", + "blockNumber": "0x1b8", + "blockTimestamp": "0x6a5df9fc", + "transactionHash": "0xe9dcca67bad1bf3d2f20f99c96867760413b55f8a3577f0132efbe955c0919f5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x9eb141226706573fc40ac654f064f438955d52d50a6b462b46672c53db4b399abd918ec577216e3105b2c4151f82dfdb42e59d674d5c8dafc2d974ff0b649412", + "blockHash": "0x63742f3e7f80fdf761d57d938a83415ea8e3cdf95b73c9cf0a4f9c00aa0272fa", + "blockNumber": "0x1b9", + "blockTimestamp": "0x6a5df9fd", + "transactionHash": "0x10979e130a268897c8fbcebe96e13f21f632397e4328b5ebf9a88c9b6dc1c1a9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x63742f3e7f80fdf761d57d938a83415ea8e3cdf95b73c9cf0a4f9c00aa0272fa", + "blockNumber": "0x1b9", + "blockTimestamp": "0x6a5df9fd", + "transactionHash": "0x10979e130a268897c8fbcebe96e13f21f632397e4328b5ebf9a88c9b6dc1c1a9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xbd918ec577216e3105b2c4151f82dfdb42e59d674d5c8dafc2d974ff0b64941219150f0ca6a946b793b5d2a31c38118a5d1a458f4a9cb94b39c688469b1162a2", + "blockHash": "0x8fe2eb89712da15f02d707888597df16ed1ea7d8fc0af972dc699982db5d6665", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5df9fd", + "transactionHash": "0x0e7f7e46bbfcc463f5848467f0ee8d339d9df5cdfea9330a9648be95266650b7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8fe2eb89712da15f02d707888597df16ed1ea7d8fc0af972dc699982db5d6665", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5df9fd", + "transactionHash": "0x0e7f7e46bbfcc463f5848467f0ee8d339d9df5cdfea9330a9648be95266650b7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x19150f0ca6a946b793b5d2a31c38118a5d1a458f4a9cb94b39c688469b1162a23084654425b40d4f974aff9f199fe92cf89e3b798c55b8aabc0ea4e9ae161a16", + "blockHash": "0x922930082d29bafe0570722a46b263f7fbd1e80a81c6d776f15fc6887418dfaa", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5df9fe", + "transactionHash": "0xb5a7706d2954029e191ac298bad326d74e3db7070bdcfa1f04f722f3d03d4f8e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x922930082d29bafe0570722a46b263f7fbd1e80a81c6d776f15fc6887418dfaa", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5df9fe", + "transactionHash": "0xb5a7706d2954029e191ac298bad326d74e3db7070bdcfa1f04f722f3d03d4f8e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x3084654425b40d4f974aff9f199fe92cf89e3b798c55b8aabc0ea4e9ae161a16dbcba8fccd40785626f0dcbf58196668c4f1dbf62f5550c8c190c9a831f12edf", + "blockHash": "0xcdcdfe7cb24540cfa7ca75430811e350f162989b37ff9aa3aee973ea1187b9f5", + "blockNumber": "0x1be", + "blockTimestamp": "0x6a5df9ff", + "transactionHash": "0xf57dc858359dff188b22ff48a61ed744001c53bd981f48c131fbda58cc49e4b8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcdcdfe7cb24540cfa7ca75430811e350f162989b37ff9aa3aee973ea1187b9f5", + "blockNumber": "0x1be", + "blockTimestamp": "0x6a5df9ff", + "transactionHash": "0xf57dc858359dff188b22ff48a61ed744001c53bd981f48c131fbda58cc49e4b8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xdbcba8fccd40785626f0dcbf58196668c4f1dbf62f5550c8c190c9a831f12edf722b8a2b2bd33d618902457457948825bbc413c8ce873093d5767f9c33846f90", + "blockHash": "0x32145577e359555b1d8d6fd7676cb0e9c21429053384f7a2a2e514788ed80e47", + "blockNumber": "0x1bf", + "blockTimestamp": "0x6a5df9ff", + "transactionHash": "0x67d05ae2bf0d2f69b36151a4f540790010aff43035c358f37ee6b431c65e641c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x32145577e359555b1d8d6fd7676cb0e9c21429053384f7a2a2e514788ed80e47", + "blockNumber": "0x1bf", + "blockTimestamp": "0x6a5df9ff", + "transactionHash": "0x67d05ae2bf0d2f69b36151a4f540790010aff43035c358f37ee6b431c65e641c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x722b8a2b2bd33d618902457457948825bbc413c8ce873093d5767f9c33846f902150caa4c72fb5f0ee83f6a8f990c02697a917fec2a162930e9abd70b91d2319", + "blockHash": "0x22f0cd503698e680e68da2910b10fc835b6a66a8c9956bcc2fc5abb34bb6177f", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfa00", + "transactionHash": "0xfdb3e2eccefff4151a00aff3fe79018fbed89a4a86f288ebf1cf6e6d315e53b1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x22f0cd503698e680e68da2910b10fc835b6a66a8c9956bcc2fc5abb34bb6177f", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfa00", + "transactionHash": "0xfdb3e2eccefff4151a00aff3fe79018fbed89a4a86f288ebf1cf6e6d315e53b1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x2150caa4c72fb5f0ee83f6a8f990c02697a917fec2a162930e9abd70b91d2319ca56bb1e6a1987fcc7c2b23012a32322841100f147821ab64dc3ebae99225fe3", + "blockHash": "0x54b08a7e45e049308a9081b961ce313fc28c7ad98d36c1f7bb9c2e6f13a6c058", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfa01", + "transactionHash": "0xbd059ecbc7e4769790b69afe35606152aae185d621b3cd4bf8412877d3014122", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x54b08a7e45e049308a9081b961ce313fc28c7ad98d36c1f7bb9c2e6f13a6c058", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfa01", + "transactionHash": "0xbd059ecbc7e4769790b69afe35606152aae185d621b3cd4bf8412877d3014122", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xca56bb1e6a1987fcc7c2b23012a32322841100f147821ab64dc3ebae99225fe30da49874450c9de962d977c1796e7651d96502fb89287efd496d33b4c3a73dfd", + "blockHash": "0x16f219b299cc82f58c4cdd04414bba4f9e8d8bb013e55b407926592b4b9973f5", + "blockNumber": "0x1c4", + "blockTimestamp": "0x6a5dfa02", + "transactionHash": "0x78c16d5e867e7c0437c6ffb7df2bdd5d6ff85b4f7f14a2ed3e67a3a6e84e5109", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x16f219b299cc82f58c4cdd04414bba4f9e8d8bb013e55b407926592b4b9973f5", + "blockNumber": "0x1c4", + "blockTimestamp": "0x6a5dfa02", + "transactionHash": "0x78c16d5e867e7c0437c6ffb7df2bdd5d6ff85b4f7f14a2ed3e67a3a6e84e5109", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x0da49874450c9de962d977c1796e7651d96502fb89287efd496d33b4c3a73dfd3c4f3ec944fee064fefbe31ee8f11dcd0749f00289562ba47bb4e94eb6938116", + "blockHash": "0xd9c3709ccac94177f1a29e0492a7504f9179a4f59cd9c6408ff5839ae565d9df", + "blockNumber": "0x1c5", + "blockTimestamp": "0x6a5dfa02", + "transactionHash": "0xd4a4fc3346fef8435602dec7360962808ae0b675ece5c7e76064cdad4eb22b7d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd9c3709ccac94177f1a29e0492a7504f9179a4f59cd9c6408ff5839ae565d9df", + "blockNumber": "0x1c5", + "blockTimestamp": "0x6a5dfa02", + "transactionHash": "0xd4a4fc3346fef8435602dec7360962808ae0b675ece5c7e76064cdad4eb22b7d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x3c4f3ec944fee064fefbe31ee8f11dcd0749f00289562ba47bb4e94eb6938116401f716ab6046dc8fc7c62674aa87abcf761eb4d172dd8bd1ca4f9d4909d51e9", + "blockHash": "0x39502a3757409cdd01a4dd2c73f834b5c0ada48872d1c5b5b06a28dcaa1d7369", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfa03", + "transactionHash": "0x843a84f161a42ee5ff3435c6e5fde2675fa18185316a930adaf986d7b87af85f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x39502a3757409cdd01a4dd2c73f834b5c0ada48872d1c5b5b06a28dcaa1d7369", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfa03", + "transactionHash": "0x843a84f161a42ee5ff3435c6e5fde2675fa18185316a930adaf986d7b87af85f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x401f716ab6046dc8fc7c62674aa87abcf761eb4d172dd8bd1ca4f9d4909d51e91ba21445e358d25d993e33e5a6353265c2341544b7c543dbeb59c25c6b489fac", + "blockHash": "0xb20c9b2752e11716be936a78774326870b5ab939ea3406aee6cc09f06ffcab8f", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfa03", + "transactionHash": "0x2e330bdcdb8ff1dc567d4b8a012280408944e0c9225ca6454a99152bd572cc52", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb20c9b2752e11716be936a78774326870b5ab939ea3406aee6cc09f06ffcab8f", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfa03", + "transactionHash": "0x2e330bdcdb8ff1dc567d4b8a012280408944e0c9225ca6454a99152bd572cc52", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x1ba21445e358d25d993e33e5a6353265c2341544b7c543dbeb59c25c6b489faca75bc1306f1e468baf789ac4c0b76b2ae72fbd7926e6ace54f2f14f3c9de75ad", + "blockHash": "0xae611e31972b2a4a4a0650416efff2b53c469d5390aa08f9617c5754c81f85a9", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfa04", + "transactionHash": "0x03d13146405d64da9b48b8febdd66b132f2e5c6758bdd4ddebb46ea5c009ed44", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xae611e31972b2a4a4a0650416efff2b53c469d5390aa08f9617c5754c81f85a9", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfa04", + "transactionHash": "0x03d13146405d64da9b48b8febdd66b132f2e5c6758bdd4ddebb46ea5c009ed44", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xa75bc1306f1e468baf789ac4c0b76b2ae72fbd7926e6ace54f2f14f3c9de75ad0d06e5bc3e09b42253c582a93cf0ddee2e0cd316499bf9d61903c256a95675a5", + "blockHash": "0xd25250fa57358c91231a3ab2d926dae1f02c7c9d6963fd6262899c7a4798195d", + "blockNumber": "0x1cb", + "blockTimestamp": "0x6a5dfa04", + "transactionHash": "0xef505b395b7f449b130c983c50f59b455b6ed99b76ec58c090e77b5f9511d0f1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd25250fa57358c91231a3ab2d926dae1f02c7c9d6963fd6262899c7a4798195d", + "blockNumber": "0x1cb", + "blockTimestamp": "0x6a5dfa04", + "transactionHash": "0xef505b395b7f449b130c983c50f59b455b6ed99b76ec58c090e77b5f9511d0f1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x0d06e5bc3e09b42253c582a93cf0ddee2e0cd316499bf9d61903c256a95675a5e0961990cce6a22c39b629e0716771092025fb6880f022e4238f8b357b6b7758", + "blockHash": "0x49e96c28b6c1e94973bd32917b12ca2a04a217aa0312d6849b824312dbedb80e", + "blockNumber": "0x1cd", + "blockTimestamp": "0x6a5dfa05", + "transactionHash": "0x2f0102128c9b6e4e3fcc56788a3de9a0ede506f8c6f0c95c42959531c61f6644", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x49e96c28b6c1e94973bd32917b12ca2a04a217aa0312d6849b824312dbedb80e", + "blockNumber": "0x1cd", + "blockTimestamp": "0x6a5dfa05", + "transactionHash": "0x2f0102128c9b6e4e3fcc56788a3de9a0ede506f8c6f0c95c42959531c61f6644", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xe0961990cce6a22c39b629e0716771092025fb6880f022e4238f8b357b6b7758d8f550a62ff7d4630583ea266a4c449fd90fe94a3f706f2821e48ebcd492190c", + "blockHash": "0x205abac2b6035db909aed71ba1faf140ef5368b232fcba89dc3c4c51c8dc8e83", + "blockNumber": "0x1ce", + "blockTimestamp": "0x6a5dfa06", + "transactionHash": "0x313ca71e98f59417413ffc6281586d530f35670053d79b75142350089b6494a0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x205abac2b6035db909aed71ba1faf140ef5368b232fcba89dc3c4c51c8dc8e83", + "blockNumber": "0x1ce", + "blockTimestamp": "0x6a5dfa06", + "transactionHash": "0x313ca71e98f59417413ffc6281586d530f35670053d79b75142350089b6494a0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xd8f550a62ff7d4630583ea266a4c449fd90fe94a3f706f2821e48ebcd492190cb4e4b3a8e83f6ffe9d2a44217f4e79c2c738d9dd00c2e4f304d1703c6f8b444e", + "blockHash": "0xbebd195415a0e6cb3e41e60a3ba8905a29ba731477a45aa06b3523b6edfdfbdb", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfa07", + "transactionHash": "0xd19f488ff4b5794d8f46b830ea245200d1ba4193ee43a395840851297cd83f18", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbebd195415a0e6cb3e41e60a3ba8905a29ba731477a45aa06b3523b6edfdfbdb", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfa07", + "transactionHash": "0xd19f488ff4b5794d8f46b830ea245200d1ba4193ee43a395840851297cd83f18", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xb4e4b3a8e83f6ffe9d2a44217f4e79c2c738d9dd00c2e4f304d1703c6f8b444e39ed12c51de4eaf7601557f905a7021f61ec2b256a678fbd05c776b4ba1cb362", + "blockHash": "0xdc974976c59b3fcade663936f1762e262446b690c2f6b5b7480ec67d3eb3f8be", + "blockNumber": "0x1d1", + "blockTimestamp": "0x6a5dfa07", + "transactionHash": "0xbc0d88107567c9cb67b1bdfe1dea9d9c4300d28bf7fd203009c0482101b34e20", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdc974976c59b3fcade663936f1762e262446b690c2f6b5b7480ec67d3eb3f8be", + "blockNumber": "0x1d1", + "blockTimestamp": "0x6a5dfa07", + "transactionHash": "0xbc0d88107567c9cb67b1bdfe1dea9d9c4300d28bf7fd203009c0482101b34e20", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x39ed12c51de4eaf7601557f905a7021f61ec2b256a678fbd05c776b4ba1cb362f7ee963c1480de396d0eb47c1cf482445db10ef322c10fb66b4e09f796180de5", + "blockHash": "0x6c1df3d7c2c99a4846220807894312eacf52092a59cbf6b19dadb67d377edb0d", + "blockNumber": "0x1d3", + "blockTimestamp": "0x6a5dfa08", + "transactionHash": "0xcd040d980967266bdca71d45e643a17104a253a25477bcd3f332a9fb4933d9b4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6c1df3d7c2c99a4846220807894312eacf52092a59cbf6b19dadb67d377edb0d", + "blockNumber": "0x1d3", + "blockTimestamp": "0x6a5dfa08", + "transactionHash": "0xcd040d980967266bdca71d45e643a17104a253a25477bcd3f332a9fb4933d9b4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xf7ee963c1480de396d0eb47c1cf482445db10ef322c10fb66b4e09f796180de53bc92d714d8ae2748211afc78eb6f647499c7187e1d1dd2e616fc81b962183a7", + "blockHash": "0xdeeef7631471d8137c411ffd826285d862262949ccf0834dbe26d59cf3a140eb", + "blockNumber": "0x1d4", + "blockTimestamp": "0x6a5dfa08", + "transactionHash": "0x26d197afc26b08e36f2a769378ad1e1d4beee29cc72a101a9456aaa504d4c375", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdeeef7631471d8137c411ffd826285d862262949ccf0834dbe26d59cf3a140eb", + "blockNumber": "0x1d4", + "blockTimestamp": "0x6a5dfa08", + "transactionHash": "0x26d197afc26b08e36f2a769378ad1e1d4beee29cc72a101a9456aaa504d4c375", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x3bc92d714d8ae2748211afc78eb6f647499c7187e1d1dd2e616fc81b962183a7c35c21e0ea1df16bae2b6b0cebc9fd12a0885ab4a97e0acb0c9f6344f93bbe13", + "blockHash": "0x4f37a03ab57eb777b3bb57506b389f09efc6435bec4dadbcb79909126f25ab9e", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfa09", + "transactionHash": "0x184ab116aab0df338513de02479da71ea3546bd7448ad5cb8cb05a4a4201f078", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4f37a03ab57eb777b3bb57506b389f09efc6435bec4dadbcb79909126f25ab9e", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfa09", + "transactionHash": "0x184ab116aab0df338513de02479da71ea3546bd7448ad5cb8cb05a4a4201f078", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xc35c21e0ea1df16bae2b6b0cebc9fd12a0885ab4a97e0acb0c9f6344f93bbe13d9745bfd8e5ebcb1ee4dee6699110accc7fccfdb291fe8e01fe297a600171015", + "blockHash": "0x5bc35dc6700bff9d715fab2e10f09684317792b4d0f59b11731be50d7a5c7a0d", + "blockNumber": "0x1d7", + "blockTimestamp": "0x6a5dfa09", + "transactionHash": "0x8b5d8a0572662d80c89f4e9a8cfdeab5cfe3755e2ce9c3a621bc3f9cadf71a74", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5bc35dc6700bff9d715fab2e10f09684317792b4d0f59b11731be50d7a5c7a0d", + "blockNumber": "0x1d7", + "blockTimestamp": "0x6a5dfa09", + "transactionHash": "0x8b5d8a0572662d80c89f4e9a8cfdeab5cfe3755e2ce9c3a621bc3f9cadf71a74", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xd9745bfd8e5ebcb1ee4dee6699110accc7fccfdb291fe8e01fe297a60017101542e058f92d2e40e5f6fce8ca22f2c3658d40bf33c3bdadee961cc518115084a1", + "blockHash": "0xed30d1b37c37afc8a505d65ca927d566080f812cd29433117c3a6543d12ec20d", + "blockNumber": "0x1d9", + "blockTimestamp": "0x6a5dfa0a", + "transactionHash": "0xb3c34461e06d20fb5b760d5d766b66bed26267b774b53457fbdc51a88ea0ba4c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xed30d1b37c37afc8a505d65ca927d566080f812cd29433117c3a6543d12ec20d", + "blockNumber": "0x1d9", + "blockTimestamp": "0x6a5dfa0a", + "transactionHash": "0xb3c34461e06d20fb5b760d5d766b66bed26267b774b53457fbdc51a88ea0ba4c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x42e058f92d2e40e5f6fce8ca22f2c3658d40bf33c3bdadee961cc518115084a1cce2669a5c05d3b6db0a4869a3cf19af7fb2ca44f344018d435a7a63aacd1d70", + "blockHash": "0x6a58903a0c879bd4385cb7d5913d69b186665f6790b670298c9a7ce648702441", + "blockNumber": "0x1da", + "blockTimestamp": "0x6a5dfa0b", + "transactionHash": "0xa0783f0f874b00057e1692a74051538320b23aa5912cf7b669bc830a72632999", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6a58903a0c879bd4385cb7d5913d69b186665f6790b670298c9a7ce648702441", + "blockNumber": "0x1da", + "blockTimestamp": "0x6a5dfa0b", + "transactionHash": "0xa0783f0f874b00057e1692a74051538320b23aa5912cf7b669bc830a72632999", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xcce2669a5c05d3b6db0a4869a3cf19af7fb2ca44f344018d435a7a63aacd1d70a75dca110c2a5da47646575e355e318af815690813d396203af7c54ee2d28784", + "blockHash": "0x28bbf95319bed6cb241aeeb7dc318cf5371fb8ee1c57b18df30876cb2d4bb465", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfa0c", + "transactionHash": "0x5aa63514a34c1c2a210349f8f1b80e81655fab0ae72da1f0c89777b6459dccce", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x28bbf95319bed6cb241aeeb7dc318cf5371fb8ee1c57b18df30876cb2d4bb465", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfa0c", + "transactionHash": "0x5aa63514a34c1c2a210349f8f1b80e81655fab0ae72da1f0c89777b6459dccce", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xa75dca110c2a5da47646575e355e318af815690813d396203af7c54ee2d287845def76df6c855d6097c5d637b101ab13575c419b1883542786fbb1e2ac240127", + "blockHash": "0x89a9300ae2776aeb78310c260a82be4783026e0c24cad179282f9f2d23a6879d", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfa0c", + "transactionHash": "0xda55d97af4d4bb67dfb2d5c2468ffeef866646b127811ba34d02a76d70767e8f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x89a9300ae2776aeb78310c260a82be4783026e0c24cad179282f9f2d23a6879d", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfa0c", + "transactionHash": "0xda55d97af4d4bb67dfb2d5c2468ffeef866646b127811ba34d02a76d70767e8f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x5def76df6c855d6097c5d637b101ab13575c419b1883542786fbb1e2ac240127d4deb92c819aed2267c058ac1c78e253402c60bf70a5c560cfdce2416ac41b78", + "blockHash": "0xd2e70a4b1d33fc08621be16160d82b249e9f837fcbdf74ce6bbe4fd68677004a", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfa0d", + "transactionHash": "0xd5cafd04b41b2263bf5663afb71fe534b03bcf1ea93947f30b12c015f9e0c028", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd2e70a4b1d33fc08621be16160d82b249e9f837fcbdf74ce6bbe4fd68677004a", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfa0d", + "transactionHash": "0xd5cafd04b41b2263bf5663afb71fe534b03bcf1ea93947f30b12c015f9e0c028", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xd4deb92c819aed2267c058ac1c78e253402c60bf70a5c560cfdce2416ac41b785350873ac14e5bdef928aae76d7351abce0a3c981b4a20848b3d2884262701f8", + "blockHash": "0x31c494ccbb439a89fe8a5234820cac67b22d0e2940dc6ef9334ab80b23a1fc48", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfa0d", + "transactionHash": "0xa085ff75cba5cfb8d229d8d29fe03642c7cc79d93d8ce150d86e297ba8a89dbc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x31c494ccbb439a89fe8a5234820cac67b22d0e2940dc6ef9334ab80b23a1fc48", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfa0d", + "transactionHash": "0xa085ff75cba5cfb8d229d8d29fe03642c7cc79d93d8ce150d86e297ba8a89dbc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x5350873ac14e5bdef928aae76d7351abce0a3c981b4a20848b3d2884262701f8a42cf0656af50d80e290aabd4edf00b492db6d92d636e544ab62d2091b2d9354", + "blockHash": "0xc4855d94a26661cf5db6136be55d32027bf98197bbad3b978d83859f2501d74d", + "blockNumber": "0x1e2", + "blockTimestamp": "0x6a5dfa0e", + "transactionHash": "0xc96b27b9e07e50e59ec0ba9ab503f7a41be240758c044e768e31fe44c2ca461a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc4855d94a26661cf5db6136be55d32027bf98197bbad3b978d83859f2501d74d", + "blockNumber": "0x1e2", + "blockTimestamp": "0x6a5dfa0e", + "transactionHash": "0xc96b27b9e07e50e59ec0ba9ab503f7a41be240758c044e768e31fe44c2ca461a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xa42cf0656af50d80e290aabd4edf00b492db6d92d636e544ab62d2091b2d935479640a6fc809eb09aa9e42291c2791c28bdd40867c09c6795a7e7b738b4dded9", + "blockHash": "0x4138983ad6c5e0bc72ec751ce96ce29fd598444ead4afaf86c918f7deb57ea61", + "blockNumber": "0x1e3", + "blockTimestamp": "0x6a5dfa0f", + "transactionHash": "0x27695dbf1f48375b1cbffd272eb842188293fc2fa58b1f5cf672bd9afb4efd05", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4138983ad6c5e0bc72ec751ce96ce29fd598444ead4afaf86c918f7deb57ea61", + "blockNumber": "0x1e3", + "blockTimestamp": "0x6a5dfa0f", + "transactionHash": "0x27695dbf1f48375b1cbffd272eb842188293fc2fa58b1f5cf672bd9afb4efd05", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x79640a6fc809eb09aa9e42291c2791c28bdd40867c09c6795a7e7b738b4dded92d7ec0e0b546a8a28571c584c2ef2d3372f3fb2d23e1d4722252864ab55a4b2f", + "blockHash": "0x426ab01429d45f5c95ca8103ae05e757f488f61ac281f5d349b0205c2db26f4e", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfa0f", + "transactionHash": "0xd1e04e3b8139869881abbed7ab6bde568ff6905c54aca44c359e5a6b5432ecc4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x426ab01429d45f5c95ca8103ae05e757f488f61ac281f5d349b0205c2db26f4e", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfa0f", + "transactionHash": "0xd1e04e3b8139869881abbed7ab6bde568ff6905c54aca44c359e5a6b5432ecc4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x2d7ec0e0b546a8a28571c584c2ef2d3372f3fb2d23e1d4722252864ab55a4b2fdc6aefa3b045c6af85e09a47e7255292da1bffba3b15986ad938c4d2d77bd00e", + "blockHash": "0x47099e87621c5b4a4b5285dc23bf1aa3c1b0f4feabd6a4de5a528cd31951b2a9", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfa10", + "transactionHash": "0x2021310c75a96c887fe3bb5142ae93d6feeca47aa66580b68de2aae458e2debc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x47099e87621c5b4a4b5285dc23bf1aa3c1b0f4feabd6a4de5a528cd31951b2a9", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfa10", + "transactionHash": "0x2021310c75a96c887fe3bb5142ae93d6feeca47aa66580b68de2aae458e2debc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xdc6aefa3b045c6af85e09a47e7255292da1bffba3b15986ad938c4d2d77bd00eb70fece3fdc52926ba7568b68cfe7f8212801ca75081ffba087a4eecf1106eff", + "blockHash": "0xba645999a4066782231b49c253c37d82b712a4b0f88d3fc908407b2ad55e6ca9", + "blockNumber": "0x1e8", + "blockTimestamp": "0x6a5dfa11", + "transactionHash": "0xa5cb9bb0a6a09aea280573edbc5c551b470531be2e87c983a8c11077316572b2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xba645999a4066782231b49c253c37d82b712a4b0f88d3fc908407b2ad55e6ca9", + "blockNumber": "0x1e8", + "blockTimestamp": "0x6a5dfa11", + "transactionHash": "0xa5cb9bb0a6a09aea280573edbc5c551b470531be2e87c983a8c11077316572b2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xb70fece3fdc52926ba7568b68cfe7f8212801ca75081ffba087a4eecf1106eff67497230b0b83ec18d591b5f6177a9006fcb6f6addd035b6c46784d0e3b6e4f3", + "blockHash": "0x567d33889aa9f3829c6779969da54126ed6a6a714f4d36b6eafad9d5f33b2e3a", + "blockNumber": "0x1e9", + "blockTimestamp": "0x6a5dfa11", + "transactionHash": "0x605488d357e0f4e095a470a31fa1880d9f444db9b4b876b66026018a58407ed3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x567d33889aa9f3829c6779969da54126ed6a6a714f4d36b6eafad9d5f33b2e3a", + "blockNumber": "0x1e9", + "blockTimestamp": "0x6a5dfa11", + "transactionHash": "0x605488d357e0f4e095a470a31fa1880d9f444db9b4b876b66026018a58407ed3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x67497230b0b83ec18d591b5f6177a9006fcb6f6addd035b6c46784d0e3b6e4f34a820b3886ddc0fa5f28e19c96aaf0fc6f9aafbf8c93429e41cd7a61d2b93bbf", + "blockHash": "0xbe9c24040be6dbb9adb5199e3c44a295753ffc8c4c4010808c400e328010fd63", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfa12", + "transactionHash": "0x1cc2e43dcd598dbb036095f1e4767d95d9ef45abd24eaf6c9f7153b674d9c8cb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbe9c24040be6dbb9adb5199e3c44a295753ffc8c4c4010808c400e328010fd63", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfa12", + "transactionHash": "0x1cc2e43dcd598dbb036095f1e4767d95d9ef45abd24eaf6c9f7153b674d9c8cb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x4a820b3886ddc0fa5f28e19c96aaf0fc6f9aafbf8c93429e41cd7a61d2b93bbfe3fcf9e01259f3f6414ed3ba06e54cfe2e73fe0aa64d67daafc51331f2353b14", + "blockHash": "0xd7a3e22d7746839802e18c0db1eb5ae828710638552fd3e5ca8a4523f657f912", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfa12", + "transactionHash": "0x908f2b9e6809ca7933f63f0f689350163652e2984e6303175bb7440adec8cf47", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd7a3e22d7746839802e18c0db1eb5ae828710638552fd3e5ca8a4523f657f912", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfa12", + "transactionHash": "0x908f2b9e6809ca7933f63f0f689350163652e2984e6303175bb7440adec8cf47", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0xe3fcf9e01259f3f6414ed3ba06e54cfe2e73fe0aa64d67daafc51331f2353b144af3a8d4f5751ede08a51a6fbfaa21d2ca61865ddf2a80cf89ebb669bc3eb801", + "blockHash": "0xdf4cdf29e0ed3c689cd03e275b110aff9b28ede0971dc290f8ebaab7d3a9a57d", + "blockNumber": "0x1ee", + "blockTimestamp": "0x6a5dfa13", + "transactionHash": "0x6e34fdea7373c1ef6e0e000cec9cc6f92c339ff827785f2fe3f5c83d01aae9ac", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdf4cdf29e0ed3c689cd03e275b110aff9b28ede0971dc290f8ebaab7d3a9a57d", + "blockNumber": "0x1ee", + "blockTimestamp": "0x6a5dfa13", + "transactionHash": "0x6e34fdea7373c1ef6e0e000cec9cc6f92c339ff827785f2fe3f5c83d01aae9ac", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x4af3a8d4f5751ede08a51a6fbfaa21d2ca61865ddf2a80cf89ebb669bc3eb8012336acfc05624e99d1d3ea1e456bd0487ca860221d5cd9a23415547e4ab140e9", + "blockHash": "0x60befbc026622a3bc02fe504c7390cce47fbd46e26d27e292f8b29eb4f0394de", + "blockNumber": "0x1ef", + "blockTimestamp": "0x6a5dfa14", + "transactionHash": "0xfbb025f73508baaf01603cda8d9dbca59786ff614f1884f5b2c034fad7531f41", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x60befbc026622a3bc02fe504c7390cce47fbd46e26d27e292f8b29eb4f0394de", + "blockNumber": "0x1ef", + "blockTimestamp": "0x6a5dfa14", + "transactionHash": "0xfbb025f73508baaf01603cda8d9dbca59786ff614f1884f5b2c034fad7531f41", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x2336acfc05624e99d1d3ea1e456bd0487ca860221d5cd9a23415547e4ab140e98cb0d6571d3a550038bce8346e5bee21464d999d85b018f94a32a873cf2afaf7", + "blockHash": "0x5ecc189546acd607feb8fad77849bc0db931c71550d6b9a19a7dafd8db99ab5d", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dfa14", + "transactionHash": "0x9f015d1096dd4e9e18943d3a28e8664376d765bbf6de1f5ba0b223772b06d592", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5ecc189546acd607feb8fad77849bc0db931c71550d6b9a19a7dafd8db99ab5d", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dfa14", + "transactionHash": "0x9f015d1096dd4e9e18943d3a28e8664376d765bbf6de1f5ba0b223772b06d592", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x8cb0d6571d3a550038bce8346e5bee21464d999d85b018f94a32a873cf2afaf7933d8f4671de1b5c8e0042300618bb75a5f0a9e4976b163a47db144be402f5d8", + "blockHash": "0xd61b49375af3e4a450de00817523a325ed8defa37a8e00359055981b2886dec9", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dfa15", + "transactionHash": "0x0172c7fb42425fb3c97dd1827421f6715c2bd2357f24c7b8b7604ae684d4297c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd61b49375af3e4a450de00817523a325ed8defa37a8e00359055981b2886dec9", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dfa15", + "transactionHash": "0x0172c7fb42425fb3c97dd1827421f6715c2bd2357f24c7b8b7604ae684d4297c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a" + ], + "data": "0x933d8f4671de1b5c8e0042300618bb75a5f0a9e4976b163a47db144be402f5d8158841fb1bd35f0074f7dbc452e394568dd4004c39c47709bdbd2d9a078fd497", + "blockHash": "0x4f1021ce13f28331c3e155a2758c2daa5148ef45f08a41f641ea3a25796fab9d", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dfa16", + "transactionHash": "0xde1f74d4cdd042f3da70fe03d84515465135170ab4a718a0a50825e5585e3044", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4f1021ce13f28331c3e155a2758c2daa5148ef45f08a41f641ea3a25796fab9d", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dfa16", + "transactionHash": "0xde1f74d4cdd042f3da70fe03d84515465135170ab4a718a0a50825e5585e3044", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a", + "0x0000000000000000000000006b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7" + ], + "data": "0x", + "blockHash": "0x6fd1dbb5390fa4ff41ed2559d93c65687a0b8279f66d990a852dfabe7ee8dae8", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dfa16", + "transactionHash": "0xa1d35f0a5f25e68f9706b326b9c9c0d15bc0d44ded941f5f72f1aa9d78d25a97", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000024e86000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6fd1dbb5390fa4ff41ed2559d93c65687a0b8279f66d990a852dfabe7ee8dae8", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dfa16", + "transactionHash": "0xa1d35f0a5f25e68f9706b326b9c9c0d15bc0d44ded941f5f72f1aa9d78d25a97", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x99a9a56f094dee4e771db6fac2ba744d41fbd167e90726465e4059694125795f158841fb1bd35f0074f7dbc452e394568dd4004c39c47709bdbd2d9a078fd497", + "blockHash": "0xaf7d0d8120592049609787f022f69fa381ae3497b5595868662e1630c76d0e8d", + "blockNumber": "0x1f7", + "blockTimestamp": "0x6a5dfa17", + "transactionHash": "0xc9bb073fb25529e4e4d173bf424d15fa8b9b9dd9efb8482383650fb31371bd33", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0x740b50c7eda55eac8b551b703acd023b88192a077ede5d57bde9143817c08f630000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x64382572d148724c05f55d03a7e37c8e530c4a73b9fed1f94fb4b5644db2b162", + "blockNumber": "0x1f8", + "blockTimestamp": "0x6a5dfa18", + "transactionHash": "0x331cd77f6bcfa0ea55f1fa765f87047dfd96801d0d775a0933f1170ac34ad196", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3", + "0x99a9a56f094dee4e771db6fac2ba744d41fbd167e90726465e4059694125795f", + "0x740b50c7eda55eac8b551b703acd023b88192a077ede5d57bde9143817c08f63" + ], + "data": "0xa03cc2ba6a8c241abf00b971f583a25606819ce68b589557e7f71adc659fadb8", + "blockHash": "0x64382572d148724c05f55d03a7e37c8e530c4a73b9fed1f94fb4b5644db2b162", + "blockNumber": "0x1f8", + "blockTimestamp": "0x6a5dfa18", + "transactionHash": "0x331cd77f6bcfa0ea55f1fa765f87047dfd96801d0d775a0933f1170ac34ad196", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0xdf23351ec88e207130767a77f7556e8c329c75773ed5f96a675f41d2910eb6a89f6c76eb60c9027359c22cc5d8d93e05be7fd37aa45d9c07e1162d2fc6cdfe58", + "blockHash": "0x7a99cf909862e47130608bb66ed51963f83ae54dea0313343b8d55441a10addd", + "blockNumber": "0x1fa", + "blockTimestamp": "0x6a5dfa18", + "transactionHash": "0xb11ab228659ce695c78d344b2b6d285eb5c3202b111be29e9ea0e89cb8fdae38", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7a99cf909862e47130608bb66ed51963f83ae54dea0313343b8d55441a10addd", + "blockNumber": "0x1fa", + "blockTimestamp": "0x6a5dfa18", + "transactionHash": "0xb11ab228659ce695c78d344b2b6d285eb5c3202b111be29e9ea0e89cb8fdae38", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x9f6c76eb60c9027359c22cc5d8d93e05be7fd37aa45d9c07e1162d2fc6cdfe58d4deb92c819aed2267c058ac1c78e253402c60bf70a5c560cfdce2416ac41b78", + "blockHash": "0x119ae599be6b3db5aabd03fd970424311c8854d7fc14a3a4b2c724d7745d194a", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dfa1a", + "transactionHash": "0x620b016b8ea23d3c98c00f9bf8e34d6ac4610703ad00d2425763d956b652e21a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x119ae599be6b3db5aabd03fd970424311c8854d7fc14a3a4b2c724d7745d194a", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dfa1a", + "transactionHash": "0x620b016b8ea23d3c98c00f9bf8e34d6ac4610703ad00d2425763d956b652e21a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0xd6d21e919de1500c0409c7cd5295dc1d96be80940c385f5c36cdca953a2bb9ed5ec1a384e2fcb3da608bf9907c7e474f15ea15e18702ccdba3c1f274b29dd078", + "blockHash": "0x4ee7486ac8fe0a09ac445a71180d92dc0f5f1727cbf7bd65e55417b051b29e35", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dfa1a", + "transactionHash": "0x9fb986ad8c00e58e11cccfa1a4594822b8552fee581f98da66f88fe31d493d3e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4ee7486ac8fe0a09ac445a71180d92dc0f5f1727cbf7bd65e55417b051b29e35", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dfa1a", + "transactionHash": "0x9fb986ad8c00e58e11cccfa1a4594822b8552fee581f98da66f88fe31d493d3e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x5ec1a384e2fcb3da608bf9907c7e474f15ea15e18702ccdba3c1f274b29dd078a42cf0656af50d80e290aabd4edf00b492db6d92d636e544ab62d2091b2d9354", + "blockHash": "0x8bc73437d4fa17e40b767c3afb1139e0f111f024b6710680f7a3b6a61b5441c9", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dfa1b", + "transactionHash": "0x54a1c2c5c2c40adf0309077318aacb3cbb113addcf0a958b8a674407809f89f7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8bc73437d4fa17e40b767c3afb1139e0f111f024b6710680f7a3b6a61b5441c9", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dfa1b", + "transactionHash": "0x54a1c2c5c2c40adf0309077318aacb3cbb113addcf0a958b8a674407809f89f7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x49107f45d3652844e2ce11006aa9c33bab643996238a8ff4ec91c3acffb42775c6f86731f850f3d13dade490e0cda4832a51ec5a89e4992be946ac09d1043b31", + "blockHash": "0xd182652ce552d0a68b4282f3df4a953ac2f04f9b9ef23913ffad77572be7b4ae", + "blockNumber": "0x200", + "blockTimestamp": "0x6a5dfa1b", + "transactionHash": "0x32d8068c1e8b80ee8234876c523179c7ed6f1b4963851babd6f1d797900912e1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd182652ce552d0a68b4282f3df4a953ac2f04f9b9ef23913ffad77572be7b4ae", + "blockNumber": "0x200", + "blockTimestamp": "0x6a5dfa1b", + "transactionHash": "0x32d8068c1e8b80ee8234876c523179c7ed6f1b4963851babd6f1d797900912e1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0xc6f86731f850f3d13dade490e0cda4832a51ec5a89e4992be946ac09d1043b312d7ec0e0b546a8a28571c584c2ef2d3372f3fb2d23e1d4722252864ab55a4b2f", + "blockHash": "0x30b9d116e8947cefbc9eb360a643c1a8ddbab19344d01ad97fa127390cbfd53b", + "blockNumber": "0x201", + "blockTimestamp": "0x6a5dfa1d", + "transactionHash": "0xa7281224d8c26421a1c8e81a826d4dde1beee9bcbed726caa1c7fe2705342f19", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x30b9d116e8947cefbc9eb360a643c1a8ddbab19344d01ad97fa127390cbfd53b", + "blockNumber": "0x201", + "blockTimestamp": "0x6a5dfa1d", + "transactionHash": "0xa7281224d8c26421a1c8e81a826d4dde1beee9bcbed726caa1c7fe2705342f19", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x2fc6f5e8c56e500c7373d4793a0f05181a962660f741f5b256630889a26d88022b77cbaff63a59897d2aa50befa0f17ee1ad7dd32e2d238ca52e88de388beade", + "blockHash": "0x37dc0aa735b8a5428804eb2d8fa0cb705852cf13b830f56c774ef10b277ffa6c", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dfa1d", + "transactionHash": "0xa5ead1f5fb1cfadda435cac168e4f2d4ff6421083ee2487b94695eaff46dacb0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x37dc0aa735b8a5428804eb2d8fa0cb705852cf13b830f56c774ef10b277ffa6c", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dfa1d", + "transactionHash": "0xa5ead1f5fb1cfadda435cac168e4f2d4ff6421083ee2487b94695eaff46dacb0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x2b77cbaff63a59897d2aa50befa0f17ee1ad7dd32e2d238ca52e88de388beadeb70fece3fdc52926ba7568b68cfe7f8212801ca75081ffba087a4eecf1106eff", + "blockHash": "0x3a776a062e9c8ca95cc0b73b33c124259231414f90a406a11c1973fb81128705", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dfa1e", + "transactionHash": "0x149d6633494554ac73b8702b2bbc07957e10af8711669f2113f6061d097482bc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3a776a062e9c8ca95cc0b73b33c124259231414f90a406a11c1973fb81128705", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dfa1e", + "transactionHash": "0x149d6633494554ac73b8702b2bbc07957e10af8711669f2113f6061d097482bc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x24591a438d146becf4edf474c881b45054fc1d8722e3881ac7191b9b27acfede4d6db7529628aa6ee20de0557517426bb4df90d0d6b27b97e4d14dcd462b5fb7", + "blockHash": "0xfe3fdebea08b0764c264cc4bc6d953d14938baabbf8f62d2dc3a3a05648343af", + "blockNumber": "0x206", + "blockTimestamp": "0x6a5dfa1e", + "transactionHash": "0xb34c074bc195063e4ae62a40529c106b58561761ab2c4f9bdf185b8b9fb4464c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfe3fdebea08b0764c264cc4bc6d953d14938baabbf8f62d2dc3a3a05648343af", + "blockNumber": "0x206", + "blockTimestamp": "0x6a5dfa1e", + "transactionHash": "0xb34c074bc195063e4ae62a40529c106b58561761ab2c4f9bdf185b8b9fb4464c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x4d6db7529628aa6ee20de0557517426bb4df90d0d6b27b97e4d14dcd462b5fb74a820b3886ddc0fa5f28e19c96aaf0fc6f9aafbf8c93429e41cd7a61d2b93bbf", + "blockHash": "0xcf797edda81649a7366515992fabbc8d6748d93ead1400e636eac02c48db707f", + "blockNumber": "0x207", + "blockTimestamp": "0x6a5dfa20", + "transactionHash": "0x16243dd9262b6ae695d8d87aff6f809562f5a71dc4423afaebc1a61b768fd3b4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcf797edda81649a7366515992fabbc8d6748d93ead1400e636eac02c48db707f", + "blockNumber": "0x207", + "blockTimestamp": "0x6a5dfa20", + "transactionHash": "0x16243dd9262b6ae695d8d87aff6f809562f5a71dc4423afaebc1a61b768fd3b4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0xaec3d4cb884fefef13c1e78bb41a4eb030cfe2260a1474eaf79dcaf9ef75a7337e05b53bce296d75adc8b764566403d57a0c725e7055b86849a1d568b0855c23", + "blockHash": "0x11858ed7bcb20c3dbde6eafb1be760c3b401240b9649dad90a12fb56f58b81da", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dfa20", + "transactionHash": "0x3983e22f3e8de61e315cc39f675e36c651665a26d5dc312af8d9f41d1556d204", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x11858ed7bcb20c3dbde6eafb1be760c3b401240b9649dad90a12fb56f58b81da", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dfa20", + "transactionHash": "0x3983e22f3e8de61e315cc39f675e36c651665a26d5dc312af8d9f41d1556d204", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x7e05b53bce296d75adc8b764566403d57a0c725e7055b86849a1d568b0855c234af3a8d4f5751ede08a51a6fbfaa21d2ca61865ddf2a80cf89ebb669bc3eb801", + "blockHash": "0x77232f610cbb06268fa6a722ead49997b359bf5fae7e2040aecad03dccd339f0", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dfa21", + "transactionHash": "0x610d370b15050427bfadc82dd8e44683406c4094c3806da081a289b8b50a8ec3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x77232f610cbb06268fa6a722ead49997b359bf5fae7e2040aecad03dccd339f0", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dfa21", + "transactionHash": "0x610d370b15050427bfadc82dd8e44683406c4094c3806da081a289b8b50a8ec3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x65e9967aaf9212a797b84215f3fe3b0006d9e9268fbba5bbaa17124ae50cea52c28c69bacdb169149b2103d0b8deba803407f4bcbaf5f1fd096906b1fb32e328", + "blockHash": "0x9016d0bec067f3328384909e1ccfb28fc4d9140fd0c1e7faf49545e481eb7eeb", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dfa21", + "transactionHash": "0x78471aa3cb0f04270bdf2f01cdda27d3a47384d17a12990a41724452415caff1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9016d0bec067f3328384909e1ccfb28fc4d9140fd0c1e7faf49545e481eb7eeb", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dfa21", + "transactionHash": "0x78471aa3cb0f04270bdf2f01cdda27d3a47384d17a12990a41724452415caff1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0xc28c69bacdb169149b2103d0b8deba803407f4bcbaf5f1fd096906b1fb32e3288cb0d6571d3a550038bce8346e5bee21464d999d85b018f94a32a873cf2afaf7", + "blockHash": "0xd42450a0e7bac5161f5ab9a4e40f6f21f584373a5bc95198a893013e90a66df1", + "blockNumber": "0x20d", + "blockTimestamp": "0x6a5dfa23", + "transactionHash": "0xdcbba173f7de93f0085777dfb521a99460f3935577df01dd2eaa69e41ae1ac2e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd42450a0e7bac5161f5ab9a4e40f6f21f584373a5bc95198a893013e90a66df1", + "blockNumber": "0x20d", + "blockTimestamp": "0x6a5dfa23", + "transactionHash": "0xdcbba173f7de93f0085777dfb521a99460f3935577df01dd2eaa69e41ae1ac2e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x21a5306c79bdf45383026d94728f742476f81e147cdad8b86962123b64debe854a46ee0161b5ed020fe0ad8795640da80b008c733dfc3cbff8ac5fc16c127c10", + "blockHash": "0xca8daf17f43048963c3394af24bbbdaa4de7b46709dac3ddadfc2d7717130465", + "blockNumber": "0x20f", + "blockTimestamp": "0x6a5dfa23", + "transactionHash": "0x7ce715c45a251f6e16b05ca308ea0908e0d89fb26126942e2323e8c61ef2ad86", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xca8daf17f43048963c3394af24bbbdaa4de7b46709dac3ddadfc2d7717130465", + "blockNumber": "0x20f", + "blockTimestamp": "0x6a5dfa23", + "transactionHash": "0x7ce715c45a251f6e16b05ca308ea0908e0d89fb26126942e2323e8c61ef2ad86", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3" + ], + "data": "0x4a46ee0161b5ed020fe0ad8795640da80b008c733dfc3cbff8ac5fc16c127c10158841fb1bd35f0074f7dbc452e394568dd4004c39c47709bdbd2d9a078fd497", + "blockHash": "0xecddfd7e41001b7f14b857132a8f3397be50174d3087ce5fd0394a286d638bea", + "blockNumber": "0x210", + "blockTimestamp": "0x6a5dfa24", + "transactionHash": "0x5a521da5977ac9a4c703c97a4706dbf58ace3a7cfaf5fd713ff06622afcf1b86", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xecddfd7e41001b7f14b857132a8f3397be50174d3087ce5fd0394a286d638bea", + "blockNumber": "0x210", + "blockTimestamp": "0x6a5dfa24", + "transactionHash": "0x5a521da5977ac9a4c703c97a4706dbf58ace3a7cfaf5fd713ff06622afcf1b86", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3", + "0x0000000000000000000000008e4990ef899d2fb0cac52be4d3813a0543ad4431" + ], + "data": "0x", + "blockHash": "0x3799bbc41c30972d0cf1e639c1464374e403192bcea37c1d3de2e9a62033682a", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dfa24", + "transactionHash": "0xb44c341c748cd8250b19f6b2f3a2c37f1f5d039b616ad336e253b603093a51e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000002582d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3799bbc41c30972d0cf1e639c1464374e403192bcea37c1d3de2e9a62033682a", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dfa24", + "transactionHash": "0xb44c341c748cd8250b19f6b2f3a2c37f1f5d039b616ad336e253b603093a51e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xf641e4518c487c9f6adc0425229ce5481271b913a11610a61198b0b1a46270b20000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x02468990267d930291abe3a4f6341f54a2e6f8359c26f9225350d15d7c68aaf3", + "blockNumber": "0x213", + "blockTimestamp": "0x6a5dfa26", + "transactionHash": "0x29694c03a7c3fa6910089129abdf04603d28f8dce36d0054459fb59f4a1f2445", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x0c409ad361044bd812599caf0e096676243443607119ab94651d5dbbdf056aef158841fb1bd35f0074f7dbc452e394568dd4004c39c47709bdbd2d9a078fd497", + "blockHash": "0x1bfef49185d617c6a377474388a80dd05fa99c3476e6e88d3fb1f96eb2fc5d36", + "blockNumber": "0x215", + "blockTimestamp": "0x6a5dfa26", + "transactionHash": "0xb4523a1be1c573cf9a7353f86246427d0680a6ee13da3e12c33888edba91b4dd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e", + "0xf641e4518c487c9f6adc0425229ce5481271b913a11610a61198b0b1a46270b2", + "0x0c409ad361044bd812599caf0e096676243443607119ab94651d5dbbdf056aef" + ], + "data": "0xa29999e97f80029bdd740e02ff509af828f3ceacee349a7c5e194ddd1eb42fbf", + "blockHash": "0x1bfef49185d617c6a377474388a80dd05fa99c3476e6e88d3fb1f96eb2fc5d36", + "blockNumber": "0x215", + "blockTimestamp": "0x6a5dfa26", + "transactionHash": "0xb4523a1be1c573cf9a7353f86246427d0680a6ee13da3e12c33888edba91b4dd", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xa29999e97f80029bdd740e02ff509af828f3ceacee349a7c5e194ddd1eb42fbf756c35bf9500070f21e6e54a338f981e3d0a1fdf416c85419234a4fa849d4b4a", + "blockHash": "0x2ccd96725b005eca3c24da26bdd4a1db2a0b88a026a6478b97e389fba376f37a", + "blockNumber": "0x216", + "blockTimestamp": "0x6a5dfa27", + "transactionHash": "0x69837143e210165a7ac2c701fb72f3a1a72eb7bd93c6157238434b18f46f0be5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2ccd96725b005eca3c24da26bdd4a1db2a0b88a026a6478b97e389fba376f37a", + "blockNumber": "0x216", + "blockTimestamp": "0x6a5dfa27", + "transactionHash": "0x69837143e210165a7ac2c701fb72f3a1a72eb7bd93c6157238434b18f46f0be5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x6bbdc0a78f2963551dca391f482fbb28156bcd138b6eb8434c2ef0cd0fbc988aac63f0a8230de07b6604820fdd9707a802c2d9b03382b63fc4620aad34a46cab", + "blockHash": "0x86663175fab5cf64c289a5be5a1976165dbc716103c931c602c1e08c3701bf68", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dfa28", + "transactionHash": "0x567f292ff6385ff0d44430c967f87989738a384ae8d2adeeffbadaf184210b01", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x86663175fab5cf64c289a5be5a1976165dbc716103c931c602c1e08c3701bf68", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dfa28", + "transactionHash": "0x567f292ff6385ff0d44430c967f87989738a384ae8d2adeeffbadaf184210b01", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xac63f0a8230de07b6604820fdd9707a802c2d9b03382b63fc4620aad34a46cab5ecc1a3291cbf4827fe759860b726a4a5e9c0440bf8922e1407153dbc7b75872", + "blockHash": "0xaa8b5e58698db2526bc7cd1793e859331b91b52ff8d40575593522b2dbaf7c05", + "blockNumber": "0x219", + "blockTimestamp": "0x6a5dfa29", + "transactionHash": "0x46d54868f145d0cd1a78ccd843ab0e2a944d90c8beea538079aebe24164a847e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xaa8b5e58698db2526bc7cd1793e859331b91b52ff8d40575593522b2dbaf7c05", + "blockNumber": "0x219", + "blockTimestamp": "0x6a5dfa29", + "transactionHash": "0x46d54868f145d0cd1a78ccd843ab0e2a944d90c8beea538079aebe24164a847e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x2e6bbfc79e7ad8c84d7f0ab3ee9a635b5131de686f1968987f93c5247bf60791364c457d50d27bac2b834944e9121c060d39f2089f0cf48248fb372058e5a6ea", + "blockHash": "0xb7456864d0121993e63d78787413ef91d90848ac32438f4ae63e4e1c5b1cdcb2", + "blockNumber": "0x21b", + "blockTimestamp": "0x6a5dfa29", + "transactionHash": "0xd8558e4bf2ec1cf9718136a6672ed12a45622e641e668fcc4801b8dbdf7390bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb7456864d0121993e63d78787413ef91d90848ac32438f4ae63e4e1c5b1cdcb2", + "blockNumber": "0x21b", + "blockTimestamp": "0x6a5dfa29", + "transactionHash": "0xd8558e4bf2ec1cf9718136a6672ed12a45622e641e668fcc4801b8dbdf7390bb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x364c457d50d27bac2b834944e9121c060d39f2089f0cf48248fb372058e5a6eaf2bf035f3d712b893aff25728bb6737d3c48912f57eaac8742a0f8d8eebc99ad", + "blockHash": "0xe858ffb7f99b36d886a42022c5c036a6868a2d5da72a211809c037d7e86efa5c", + "blockNumber": "0x21c", + "blockTimestamp": "0x6a5dfa2b", + "transactionHash": "0xe2d84efec52c3bfc3597e44c156751bc21243dd9a124fe0696504625b929138c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe858ffb7f99b36d886a42022c5c036a6868a2d5da72a211809c037d7e86efa5c", + "blockNumber": "0x21c", + "blockTimestamp": "0x6a5dfa2b", + "transactionHash": "0xe2d84efec52c3bfc3597e44c156751bc21243dd9a124fe0696504625b929138c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xc6c0c1d6867d2fa13d8e8b52e3f602518e3c65e67ffefd2b7c74b83a8bd7229bf804a4e56fb328cb4f439b709fcaff7e2d5d5d7565d35b23e574c09565df057c", + "blockHash": "0xdd6fcde8bc33a503435c4f2a533320097693fc10f0d733f507a86ca3dc13a8f1", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dfa2b", + "transactionHash": "0x87f45ac53eb9b4ac1f7980d9cd852376731de162e88a6e0d1a0f0403bf1b1e65", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdd6fcde8bc33a503435c4f2a533320097693fc10f0d733f507a86ca3dc13a8f1", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dfa2b", + "transactionHash": "0x87f45ac53eb9b4ac1f7980d9cd852376731de162e88a6e0d1a0f0403bf1b1e65", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xf804a4e56fb328cb4f439b709fcaff7e2d5d5d7565d35b23e574c09565df057c43690583758db2244e82337bd2c36e0ac3d58e682b35d453bbfa311481ef130c", + "blockHash": "0x99bcdc73e25f829e1737b7b521b8ae75baf7f02ac250607cccb13ba6dd2d3e0f", + "blockNumber": "0x21f", + "blockTimestamp": "0x6a5dfa2c", + "transactionHash": "0xbcb90046afdc3af2c3b1dc40d4f7bcfef68c0d82aeb6900c6eaf74190a95f346", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x99bcdc73e25f829e1737b7b521b8ae75baf7f02ac250607cccb13ba6dd2d3e0f", + "blockNumber": "0x21f", + "blockTimestamp": "0x6a5dfa2c", + "transactionHash": "0xbcb90046afdc3af2c3b1dc40d4f7bcfef68c0d82aeb6900c6eaf74190a95f346", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x48d1182a36c237ef7e5549929f19510138a9d9e6a1758f407168b507c58eb7a61a7d45bb254107d85cc9b50193d97a43c477ff96e98ddbc9921dd7af54e876ed", + "blockHash": "0x8dcd6fdcd12f52c91e062e58bd44d5f0412ae644cf5b4c984fdf9101a7ff8bb7", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dfa2d", + "transactionHash": "0x9baa70c87b9ff5d89dd3b411a94afb902788e381f1f06a66ba7f9f35086822f7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8dcd6fdcd12f52c91e062e58bd44d5f0412ae644cf5b4c984fdf9101a7ff8bb7", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dfa2d", + "transactionHash": "0x9baa70c87b9ff5d89dd3b411a94afb902788e381f1f06a66ba7f9f35086822f7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x8c44336a6993602e42f857c90a4d8a37e1ca8962faa8e1666bf3b4d641581ae57a5fcc19074f9bf032058d52a9caeeb533a63ce2e849479ade09cac9cf581646", + "blockHash": "0x3579b84794e680694e75a78a575863e49ba99f0d6e5ee9675c528ebcf0adaebf", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dfa2e", + "transactionHash": "0x511a13711c7387a4fb16ade535aab5015f05a77b7def66b4a1017af72876006d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3579b84794e680694e75a78a575863e49ba99f0d6e5ee9675c528ebcf0adaebf", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dfa2e", + "transactionHash": "0x511a13711c7387a4fb16ade535aab5015f05a77b7def66b4a1017af72876006d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x772066119182cd79a9f7700b4b2a8faa37c7427ebb758bfdd78ad5dfaa7bee7063ac20a0b97ed0be3ac6d0de78b8933fca5de8c16677c3b1ad35ed49db8439bf", + "blockHash": "0x77bfacb43f3c839376334b1d33e9be46a555fb3c1096dadd5d1d44b2a6718ceb", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dfa2e", + "transactionHash": "0xf02bc20858d8d0d3b4987c82d9db3b61f777fa00a4163745d102b3a59763c88a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x77bfacb43f3c839376334b1d33e9be46a555fb3c1096dadd5d1d44b2a6718ceb", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dfa2e", + "transactionHash": "0xf02bc20858d8d0d3b4987c82d9db3b61f777fa00a4163745d102b3a59763c88a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x5eae2716c57601944fd72c9648ddefb6801ed2f4078e744014f0d2b5ba77c3d3b33bde610466624c97995ea7633caa8364dddba3dc27b3ba001321d449e28ffa", + "blockHash": "0xd67de915343753521b5325a7e05e24de28191ce0b82872efda0ae81505c3e65e", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dfa2f", + "transactionHash": "0x3d5f2f08b255a27a648103dfd22e20dbe84db2897bb12a8dde0bd9ebadcda014", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd67de915343753521b5325a7e05e24de28191ce0b82872efda0ae81505c3e65e", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dfa2f", + "transactionHash": "0x3d5f2f08b255a27a648103dfd22e20dbe84db2897bb12a8dde0bd9ebadcda014", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xd99888ddd722fa34f188eaffdd0dd8e7a4e2ee35b3f01ff6fae0519b926500493bb418a49bcb63f68c86cbbbd0e8486817d6495e8753cc8e866f753eca9b62ab", + "blockHash": "0x36b2a09365248442b0e667885824759e2971fc8b7b48857a404d0889e87bbd1d", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dfa30", + "transactionHash": "0x11d531f87b39268d587389b960ada94beb5d193be561de3f1c6d4fbeafad3ea7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x36b2a09365248442b0e667885824759e2971fc8b7b48857a404d0889e87bbd1d", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dfa30", + "transactionHash": "0x11d531f87b39268d587389b960ada94beb5d193be561de3f1c6d4fbeafad3ea7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x80968ffeac025aca4e871b405fe01f98b5fb08b7dc0457a1c96dd37f872ae1c666f505403482d90f4b9d133b378886d9bfdd9e3815b2e858c12b8bef8fae6abb", + "blockHash": "0x27396f9846a4ac11c2a0e41e2f5ce7dd9b61d114ac0ee4ac169f2cbcaa6c731b", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dfa31", + "transactionHash": "0xd2ac35305537b1b8d0a9acdd117c1f12c35a08f30f011e76b7caa4bd3f7a8472", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x27396f9846a4ac11c2a0e41e2f5ce7dd9b61d114ac0ee4ac169f2cbcaa6c731b", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dfa31", + "transactionHash": "0xd2ac35305537b1b8d0a9acdd117c1f12c35a08f30f011e76b7caa4bd3f7a8472", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x7968685435dbfee62fa55314bd2efa4b034fd17ca9cf858a3039944650fd061316a9b33ee6ed91abc12e1e21bb5b180886aed2c71d1481b1930e7e73e8275f76", + "blockHash": "0xbaa993c93b870e701908b51c827e98877bd5d3d69bb0b0d73a5058d26cf0e512", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dfa31", + "transactionHash": "0xe6bb0d3bb4aba6767689dc5a82c2f82354cef9114540ec9cfed070042dfbd881", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbaa993c93b870e701908b51c827e98877bd5d3d69bb0b0d73a5058d26cf0e512", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dfa31", + "transactionHash": "0xe6bb0d3bb4aba6767689dc5a82c2f82354cef9114540ec9cfed070042dfbd881", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xd8be2e2af1d38dd87a451900b1dc86ebb89a223879502acb9244912d4ad3bd212a567871dd2cefce0616477f50aa1fc61774134888a24c461948f4e2fd676bf5", + "blockHash": "0x898cf89b5a6ab97a38ac17945bb35acc4d6b591f464a2cca8c77f3875bcfd75c", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dfa33", + "transactionHash": "0xc11510af9f4579eee9b69ecfda5c796f0cad1b1c65dcfa250cad7fe7e0edba39", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x898cf89b5a6ab97a38ac17945bb35acc4d6b591f464a2cca8c77f3875bcfd75c", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dfa33", + "transactionHash": "0xc11510af9f4579eee9b69ecfda5c796f0cad1b1c65dcfa250cad7fe7e0edba39", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xad78f82b792e6467937e50e72f019279852138744ddd6b82ca9fd0a76fcdac91805a35fe3e71aa14cc4074fcec047968c2f3d6033fae645175bd299c945f2cf0", + "blockHash": "0xd62f583839bed38db1996b42726ab1e13182cb82d00d3e733dc5ab9ea95282bf", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dfa33", + "transactionHash": "0x424366d206c0728415b48b027599f449dfcf31eed05e28e7d197d480df2710c5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd62f583839bed38db1996b42726ab1e13182cb82d00d3e733dc5ab9ea95282bf", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dfa33", + "transactionHash": "0x424366d206c0728415b48b027599f449dfcf31eed05e28e7d197d480df2710c5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x4bea077717400d4cbffda1eca8c3558d1afea9cd1525f7eb8d3bc3be4631051734d624a2b48ddf2c5a44d0d9985ed5a96984fc98a75720ac1c2d0ff9dc6cf9f5", + "blockHash": "0x105a1a950468098c165da702dfcc746e9925d2c7b7f7263b9326e256884fef0d", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dfa34", + "transactionHash": "0x370e5af1441113fad24a2c412a91594b7b9d4b412f6742fdd6a0b388d513f7ec", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x105a1a950468098c165da702dfcc746e9925d2c7b7f7263b9326e256884fef0d", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dfa34", + "transactionHash": "0x370e5af1441113fad24a2c412a91594b7b9d4b412f6742fdd6a0b388d513f7ec", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xfe1d425aab59012ca3d0f8b64e93b29663ee99541fd3735806eb05a165a3fca973398a04cb672448959f87b94b63f468bdc0c4bfa61fd01916779d307cdbaadb", + "blockHash": "0x954edf224d3ad1b0635c81397abb1664cd20170a61fe64c744aa500188401959", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dfa35", + "transactionHash": "0x01151cfb502144e8ccd4ba4c1cf47edf2c13f94f11e9b93f851a70f0e7059cea", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x954edf224d3ad1b0635c81397abb1664cd20170a61fe64c744aa500188401959", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dfa35", + "transactionHash": "0x01151cfb502144e8ccd4ba4c1cf47edf2c13f94f11e9b93f851a70f0e7059cea", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xc2031fb8e769516879be8881ba590453bcc50d2c9bd1c98f32895a8ffc675cbaeb5e5d6343894a6cf6922f79a9feb6546797d317413f807a2b0c5a4a371992a2", + "blockHash": "0xf87c8970a5a391ce3711791eeea13d5ab317c920ef9870672a5954d1746f4eb5", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dfa36", + "transactionHash": "0x5d0f45ac3487a5a42ef0048b49e79de25996e8e2f06e4aa8ad225b7b466d9879", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf87c8970a5a391ce3711791eeea13d5ab317c920ef9870672a5954d1746f4eb5", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dfa36", + "transactionHash": "0x5d0f45ac3487a5a42ef0048b49e79de25996e8e2f06e4aa8ad225b7b466d9879", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x576016b784a5e35fc6bf720b43b40b56835e39648552950b8991c355b3e7912d646fa1f2fbf61b5353fe430be16493a9edf76537211d5729b40efb23c5bdf205", + "blockHash": "0xfb336d75e169a6639f0019f9764134bf076b9f8c5df37f2ea58ff8ffe745d559", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dfa36", + "transactionHash": "0x397c7c16da47f55d8b90a6933cbf79e4b1f861ee6e1368b47b8d2115c402e204", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfb336d75e169a6639f0019f9764134bf076b9f8c5df37f2ea58ff8ffe745d559", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dfa36", + "transactionHash": "0x397c7c16da47f55d8b90a6933cbf79e4b1f861ee6e1368b47b8d2115c402e204", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x98120af2a40f01f3fd7e748adf73d6ca33d5d8aa6fe35c789e411df74fd0f19133bf6774344562aad1a7da64fd8783040473a2649505ae2a20d936381bbebcb5", + "blockHash": "0x73957712d863e4ab1538347edae6778b354e6275faa80dc7c1592b25a0380ff6", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dfa38", + "transactionHash": "0x550be568cc467f2d546d5cfbf79cea41797a3b1da85db8769b5da87681b5a409", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x73957712d863e4ab1538347edae6778b354e6275faa80dc7c1592b25a0380ff6", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dfa38", + "transactionHash": "0x550be568cc467f2d546d5cfbf79cea41797a3b1da85db8769b5da87681b5a409", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x414fa38e05ed4320b5e17c86f94b33759e84dc918d7b6dd2fe94fe50670e9339b43920f9611cbb342da91242f3206bdd32ce9f7b5f026bb4e768d6ee90840b77", + "blockHash": "0x7e06130b3096cabc1b5df21348b0ae1b31cb7236d2d20360c92b6b596e9d9924", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dfa38", + "transactionHash": "0x241ee618fd3d02fd088f1ee7438a62c39a9d94b0a799fdc3d8c0419c85c6e020", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7e06130b3096cabc1b5df21348b0ae1b31cb7236d2d20360c92b6b596e9d9924", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dfa38", + "transactionHash": "0x241ee618fd3d02fd088f1ee7438a62c39a9d94b0a799fdc3d8c0419c85c6e020", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x5e4ae9686b2bb4f5278ea26f24878150d6beacf078b3f7b72f419d6c543a1033450ed747d3f7c62e3adff6f537586eed8fab40aff68c1402b9f977dfcd855fa3", + "blockHash": "0xeb189ba0fe737450b17da5689b7039889d11d3aaea460e51b83a8b77189f9c60", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dfa39", + "transactionHash": "0xabdf4eafb04d706f15f54e044c4974a691054304f5fb835cf101aeff1c596b54", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xeb189ba0fe737450b17da5689b7039889d11d3aaea460e51b83a8b77189f9c60", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dfa39", + "transactionHash": "0xabdf4eafb04d706f15f54e044c4974a691054304f5fb835cf101aeff1c596b54", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0xc850edbe79fcddf4514bb9920508feb12d3fc22deaa1790e7f23a2b83713d01701685c0687443dba02ffa0772504944fbefe34c47af41567d37addfde9818f57", + "blockHash": "0xd8d6e8a09ac048288e54c6a495779bbbc7ce3c84e6f683b852d8325260453a8f", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dfa3a", + "transactionHash": "0xda04a0cd29303b06d92c2b8b113c127ff829828952f19fd7d8d8e9ef41ef8970", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd8d6e8a09ac048288e54c6a495779bbbc7ce3c84e6f683b852d8325260453a8f", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dfa3a", + "transactionHash": "0xda04a0cd29303b06d92c2b8b113c127ff829828952f19fd7d8d8e9ef41ef8970", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x58ec2d06e779c133193e1cb5c7081c63d2c75e86a60a18169ff4111a1b44d05abd1963acaf4b1d93dff8740a8f8a91b074b6a3d1a6a58bd29ba000d38e09a817", + "blockHash": "0x25d37dd7fff52dca2c812cf4eaf756ec74b9f0ee410fad6e1510d4e8b36ebe34", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dfa3b", + "transactionHash": "0x209077786a29159feba9027d05a93138555814697d612136e1da3e9e7148104d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x25d37dd7fff52dca2c812cf4eaf756ec74b9f0ee410fad6e1510d4e8b36ebe34", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dfa3b", + "transactionHash": "0x209077786a29159feba9027d05a93138555814697d612136e1da3e9e7148104d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e" + ], + "data": "0x278202535cd56c04f33cb4c341a33750e454fb54788d91de11377def496f7cd7f56c6d9ecfd14c596395440eb5fba3050265a4283723dd136d75f02ff58435eb", + "blockHash": "0x95b30eb7d56696a0ccc6acc33e3a7ad7ae0312c9eb07bccab3bf33c2f88c93bd", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dfa3b", + "transactionHash": "0x1a91a5aa9524ab65a12193e0aa87bb76d1f9258a8724ba930170e1eff64d5051", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x95b30eb7d56696a0ccc6acc33e3a7ad7ae0312c9eb07bccab3bf33c2f88c93bd", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dfa3b", + "transactionHash": "0x1a91a5aa9524ab65a12193e0aa87bb76d1f9258a8724ba930170e1eff64d5051", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9f04db7a54bd215e4ec2ad637e4201486c9f4072b96e91bc0bbfec56f3669797", + "blockNumber": "0x23d", + "blockTimestamp": "0x6a5dfa3d", + "transactionHash": "0xe6eadf9fcaf59ab78a961e12a28bec397ddbb90aa2af46655218cc5cf907c956", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xaa8d7f3430ebfac10bc265a90917afe11a0b102e8512434887d36fc93492995e", + "0xf641e4518c487c9f6adc0425229ce5481271b913a11610a61198b0b1a46270b2", + "0x0c409ad361044bd812599caf0e096676243443607119ab94651d5dbbdf056aef" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0x52030bd91284b54b2e9370dc6010954bc7c202a8d3c926d6ee842e0ed7ffcf4b", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dfa3d", + "transactionHash": "0x957d6d833576b0c770144b2683f4aa67cfb1a2474f4baa3de486b8508e768e4b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003854e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x52030bd91284b54b2e9370dc6010954bc7c202a8d3c926d6ee842e0ed7ffcf4b", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dfa3d", + "transactionHash": "0x957d6d833576b0c770144b2683f4aa67cfb1a2474f4baa3de486b8508e768e4b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000006a5dfa3ef4697b1b3175a39a944a825a9531b65f58f2c898d1b1569ad4f71da57f852c9000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd02c7a37e5dc5803f353627869926ab24ec7fc06a6dfde3a0d8599bf76f4329b", + "blockNumber": "0x240", + "blockTimestamp": "0x6a5dfa3e", + "transactionHash": "0x35008dcd01b0ac224848ba49a1780cd61e722b98a55595ce34ad911ef2410ea9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000241000000000000000000000000000000000000000000000000000000006a5dfa3e2002da10e0d7f1fd5238b8139716f72fb4d1fe4f9f2215f29a401431c49fc41000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0fc5282cd0d11979a4f069701bd9822062cf155d73836a20570721500abc3e4d", + "blockNumber": "0x241", + "blockTimestamp": "0x6a5dfa3e", + "transactionHash": "0xaa9fea3ae842976876f637171898248342cefebf112fbf5a9830091b9251d024", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000242000000000000000000000000000000000000000000000000000000006a5dfa3e35b48304d0ad1dd386e5ff6fe011143b7d50c2e906d4bb3c4febce2c5380cf7300000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf6d141cadbda45acd1e4b809b66a75db90598a5c360c35917c3e53bfede0a0a7", + "blockNumber": "0x242", + "blockTimestamp": "0x6a5dfa3e", + "transactionHash": "0x402a538fbc7ad21aaa007822ee1a19ef6df7ef9a61efbdf3c84a92ea546d1133", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xe35541c317dac796ccd0ea97eccce8f5f994c347168b896ba7ced46fbd5e0bc3", + "0x99a9a56f094dee4e771db6fac2ba744d41fbd167e90726465e4059694125795f", + "0x740b50c7eda55eac8b551b703acd023b88192a077ede5d57bde9143817c08f63" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x64f6cb8db592a60b60e9ebefefbbd5b3926c836751a688e5d59859a452592ca1", + "blockNumber": "0x343", + "blockTimestamp": "0x6a5dfa43", + "transactionHash": "0x923fec3547b91170afbefd0879550c2fc6bc071a706997f06b99d2839d91b7a2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x64f6cb8db592a60b60e9ebefefbbd5b3926c836751a688e5d59859a452592ca1", + "blockNumber": "0x343", + "blockTimestamp": "0x6a5dfa43", + "transactionHash": "0x923fec3547b91170afbefd0879550c2fc6bc071a706997f06b99d2839d91b7a2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x96fcc3c4b21c0f32d151872693c38fde678d74578f720f7376857eed847fe19a", + "0x9db03e952e42d657a4d42d91ce301508bd2bf9b9b7ad28fcb32f58d9417d3e6d", + "0xc45dc5629f224af2e1568f1243f302c6072473d8d866e8b8ace990324e34cd07" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xcd0d546021d87628327b7b86ad3de5a2a8d2c126fcf98d8b8bdcb1074e9e1b52", + "blockNumber": "0x344", + "blockTimestamp": "0x6a5dfa44", + "transactionHash": "0x77e66952d961917e45de0458a869f3dc8a8d232867c969e9dc810185d095d490", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001ba9c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcd0d546021d87628327b7b86ad3de5a2a8d2c126fcf98d8b8bdcb1074e9e1b52", + "blockNumber": "0x344", + "blockTimestamp": "0x6a5dfa44", + "transactionHash": "0x77e66952d961917e45de0458a869f3dc8a8d232867c969e9dc810185d095d490", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0xdc5586a6f64c8096a858d2ba73f04ca65bccaab1e8f305f9427d15cbfe5975e1fb413f01343d226b18b9263d210be2d8b97ee53d722bdc09d72ac3e2cd92aa7b", + "blockHash": "0x9672bca2ce1f341674f173f3f0304cbdedf2243c7c7fa8e3742cc4906054113b", + "blockNumber": "0x345", + "blockTimestamp": "0x6a5dfa45", + "transactionHash": "0x946ffe017d45ae4793020c60f2f58a55c32a14beb3c69ba4a34f558f0db39069", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x00000000000000000000000098bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "blockHash": "0x6e389e98e5d267a602496c9b0ce57d489485286444c575b230e24d2ee7ada78e", + "blockNumber": "0x346", + "blockTimestamp": "0x6a5dfa45", + "transactionHash": "0x25f1550f1426bd2c65f4a54e688cfd66dccb3a9b5bb6c6a7c0f6e6636f643612", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007dc5586a6f64c8096a858d2ba73f04ca65bccaab1e8f305f9427d15cbfe5975e1fb413f01343d226b18b9263d210be2d8b97ee53d722bdc09d72ac3e2cd92aa7b00000000000000000000000098bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "blockHash": "0x6e389e98e5d267a602496c9b0ce57d489485286444c575b230e24d2ee7ada78e", + "blockNumber": "0x346", + "blockTimestamp": "0x6a5dfa45", + "transactionHash": "0x25f1550f1426bd2c65f4a54e688cfd66dccb3a9b5bb6c6a7c0f6e6636f643612", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ] +} \ No newline at end of file diff --git a/cartesi-rollups/node/tests/fixtures/chain-recordings/multi_sybil.json b/cartesi-rollups/node/tests/fixtures/chain-recordings/multi_sybil.json new file mode 100644 index 000000000..c714c08a4 --- /dev/null +++ b/cartesi-rollups/node/tests/fixtures/chain-recordings/multi_sybil.json @@ -0,0 +1,6784 @@ +{ + "note": "multi-sybil", + "chain_id": 31337, + "from_block": 0, + "to_block": 1371, + "block_timestamps": { + "23": 1784544330, + "24": 1784544330, + "27": 1784544330, + "28": 1784544330, + "29": 1784544330, + "32": 1784544331, + "33": 1784544331, + "419": 1784544384, + "420": 1784544384, + "421": 1784544388, + "424": 1784544391, + "425": 1784544391, + "426": 1784544391, + "428": 1784544392, + "430": 1784544394, + "431": 1784544394, + "432": 1784544395, + "437": 1784544396, + "438": 1784544397, + "443": 1784544398, + "444": 1784544399, + "449": 1784544400, + "450": 1784544401, + "455": 1784544402, + "456": 1784544403, + "457": 1784544403, + "458": 1784544404, + "463": 1784544406, + "464": 1784544407, + "469": 1784544409, + "470": 1784544410, + "475": 1784544412, + "476": 1784544412, + "477": 1784544412, + "478": 1784544413, + "479": 1784544413, + "480": 1784544414, + "485": 1784544415, + "486": 1784544416, + "491": 1784544417, + "492": 1784544418, + "497": 1784545135, + "498": 1784545135, + "499": 1784545136, + "500": 1784545136, + "501": 1784545136, + "502": 1784545137, + "507": 1784545139, + "508": 1784545139, + "509": 1784545139, + "510": 1784545140, + "515": 1784545142, + "516": 1784545142, + "521": 1784545144, + "522": 1784545144, + "523": 1784545144, + "524": 1784545145, + "529": 1784545147, + "530": 1784545147, + "535": 1784545149, + "536": 1784545149, + "541": 1784545151, + "542": 1784545151, + "543": 1784545152, + "544": 1784545153, + "545": 1784545153, + "546": 1784545154, + "547": 1784545155, + "548": 1784545155, + "549": 1784545156, + "550": 1784545156, + "551": 1784545157, + "552": 1784545157, + "553": 1784545158, + "554": 1784545158, + "555": 1784545159, + "556": 1784545159, + "557": 1784545160, + "558": 1784545160, + "559": 1784545161, + "560": 1784545161, + "561": 1784545162, + "562": 1784545162, + "563": 1784545163, + "564": 1784545163, + "565": 1784545164, + "566": 1784545165, + "567": 1784545166, + "568": 1784545167, + "569": 1784545167, + "570": 1784545168, + "571": 1784545168, + "572": 1784545169, + "573": 1784545169, + "574": 1784545170, + "575": 1784545171, + "576": 1784545172, + "577": 1784545172, + "578": 1784545173, + "579": 1784545173, + "580": 1784545174, + "581": 1784545175, + "582": 1784545176, + "583": 1784545176, + "584": 1784545177, + "585": 1784545177, + "586": 1784545178, + "587": 1784545179, + "588": 1784545179, + "589": 1784545180, + "590": 1784545181, + "591": 1784545182, + "732": 1784545242, + "857": 1784545295, + "862": 1784545296, + "867": 1784545298, + "868": 1784545298, + "873": 1784545299, + "874": 1784545300, + "879": 1784545301, + "880": 1784545301, + "885": 1784545303, + "886": 1784545303, + "891": 1784545304, + "892": 1784545305, + "897": 1784545306, + "898": 1784545306, + "903": 1784545308, + "904": 1784545308, + "909": 1784545309, + "910": 1784545309, + "911": 1784545310, + "912": 1784545310, + "917": 1784545311, + "918": 1784545311, + "923": 1784545313, + "924": 1784545313, + "929": 1784545315, + "930": 1784545315, + "935": 1784545316, + "936": 1784545316, + "941": 1784545318, + "942": 1784545319, + "947": 1784545320, + "948": 1784545321, + "953": 1784545321, + "954": 1784545322, + "959": 1784545323, + "960": 1784545324, + "965": 1784545324, + "966": 1784545325, + "971": 1784545326, + "972": 1784545327, + "977": 1784545328, + "978": 1784545329, + "983": 1784545329, + "984": 1784545330, + "989": 1784545331, + "990": 1784545332, + "995": 1784545333, + "996": 1784545334, + "1001": 1784545334, + "1002": 1784545335, + "1007": 1784545336, + "1008": 1784545336, + "1009": 1784545337, + "1014": 1784545338, + "1015": 1784545339, + "1020": 1784545340, + "1021": 1784545341, + "1026": 1784545342, + "1027": 1784545343, + "1032": 1784545344, + "1033": 1784545345, + "1038": 1784545347, + "1039": 1784545347, + "1040": 1784545347, + "1041": 1784545348, + "1046": 1784545349, + "1047": 1784545350, + "1052": 1784545351, + "1053": 1784545352, + "1058": 1784545353, + "1059": 1784545354, + "1060": 1784545354, + "1061": 1784545355, + "1062": 1784545355, + "1063": 1784545356, + "1068": 1784545357, + "1069": 1784545358, + "1070": 1784545358, + "1071": 1784545359, + "1072": 1784545359, + "1073": 1784545360, + "1078": 1784545361, + "1079": 1784545362, + "1080": 1784545362, + "1081": 1784545363, + "1086": 1784545364, + "1087": 1784545365, + "1088": 1784545365, + "1089": 1784545366, + "1090": 1784545366, + "1091": 1784545367, + "1096": 1784545368, + "1097": 1784545369, + "1098": 1784545369, + "1099": 1784545370, + "1104": 1784545372, + "1105": 1784545372, + "1106": 1784545372, + "1107": 1784545373, + "1368": 1784545380, + "1369": 1784545381, + "1370": 1784545382, + "1371": 1784545382 + }, + "logs": [ + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def" + ], + "data": "0x", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xc549f89cf1ca43eddecc64ac2208f4b283b1c483", + "topics": [ + "0xf57fedb261f4593784de9abb6653acfbaf45e74182818717c6e9b39c344a2a78", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22defe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea0000000000000000000000000000000000000000000000000000000000000024b12c9ede000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad14393900000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xaf68463e16cb5595a44214bea8d366ecf7cd3410269c50f92c104b50a7829daa" + ], + "data": "0x000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad143939000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea00000000000000000000000064f6cf454348f891e837eddc99be67ea98c64602", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x6ad3188ba8f430fba0656cb0a7e839ab2020d5586ba11a1477d18f7092f8bece" + ], + "data": "0x0000000000000000000000009da58d313a19d0185ff8ad3bad7e538d9e477697", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x5", + "removed": false + }, + { + "address": "0xd02bc1f641965abbdcab968a746c5d52fe0f47ea", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x6", + "removed": false + }, + { + "address": "0x0fdab499db8a58f8d04dc6ad2b05e72252b22def", + "topics": [ + "0xdf2ebeb5a7d7df0100c0274c7cee9570954d7bebeef37db55b27204a57f65602" + ], + "data": "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea0000000000000000000000009da58d313a19d0185ff8ad3bad7e538d9e477697", + "blockHash": "0x1ca19f112159058909a0d31aa3fce922d86cd35e2f3ed462fb58369a6a01db73", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x133981c6437f602ce41869915af57df042e2a4cf5f6ba386dedf8c320ccba31d", + "transactionIndex": "0x0", + "logIndex": "0x7", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000006a5dfc4afee49ffe80ecb22715d15dc39dfbace51515eb24a1d32a9c8b3da94c7061012e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x644522e765864be7e0eb3d711dc4783f78da63d8b52de7e0e229ca78649f2a37", + "blockNumber": "0x18", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x5c1823cadd54a9e08be021d7272103d8af0b90aaefd00f625d797854708bbd58", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000006a5dfc4aca87458d33b4cd10a7c09f253e76e0528c5d149802a913927e17c84bec236c7a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x790cbfeb2f931ad35715149ff4db8b5241a1a469a75a9071c962e1607a96ef14", + "blockNumber": "0x1b", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0x92cd6ff7fb1243cc4d1098c6f713f6c7392eb9135b5b37d8c34fbfe2ac4a5bca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000006a5dfc4ae57fecb68c3d8e8cd9e07c8a312f9357e69a75e8ab876f980756cfc137a48d6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd6127e987b88c018470508fee138a8d84322e948736ba5ff5ac2b132a70fef63", + "blockNumber": "0x1c", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0xe571e4686ae563533b0cef1c7192e78cc98d03ef1bc0c205e0b728b0e853beb8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000d02bc1f641965abbdcab968a746c5d52fe0f47ea000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000006a5dfc4a9dcc996a40ba035ae4222d67b9320e3f5a7a84f84f0781791bfd46b689135ba400000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3f3ce8168bde873189839d477b29cce51c3e9b46e367c956f3e65944f8470ca5", + "blockNumber": "0x1d", + "blockTimestamp": "0x6a5dfc4a", + "transactionHash": "0xbb51539f611933a63f52ad4573679e7575c08f12d1fda2926c5e7d0f91fa0710", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0xe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821", + "blockHash": "0x1252a9c178552e9cbb9d425f1811fd78d38c8500f6f13b9ded9dfa8d8e90cf20", + "blockNumber": "0x20", + "blockTimestamp": "0x6a5dfc4b", + "transactionHash": "0xd9be04fbd243243a21edb384b212a46d0d895e064a2c4d40aac1ef40df93ac3d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xe366c3ef306160a2770c6e55551c28436ede846c", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x96d3518810435589f48da25a87536bfaa68df54ec1c9bee3c869d893f0967ce1e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821", + "blockHash": "0x232aff400b9678d82596d22b6cd8eddd3916b137bcf05d477f9af42006175307", + "blockNumber": "0x21", + "blockTimestamp": "0x6a5dfc4b", + "transactionHash": "0x2c584fa9757425b1cd0d48999045f430e1344c037b5073e3d5b795ed2f7b98f3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0xe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0xe694c76b19f56a7c1433f4d442854779ec25ca65e0ae5a15360ca59eb7d70adc", + "blockNumber": "0x1a3", + "blockTimestamp": "0x6a5dfc80", + "transactionHash": "0x48c80e6fd4bff97868831adb760abe7bae0fd2cc0e8f78a04ddef60db541f867", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x43e662d74aba69ffab78b52b085029e8392a95b7f9dbb3861de1af44f4b4094a", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5dfc80", + "transactionHash": "0x529769b00763fd8481f22fec5728eb20739223fb0cd9d1af869cab9d0cce6806", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e8243468210a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x43e662d74aba69ffab78b52b085029e8392a95b7f9dbb3861de1af44f4b4094a", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5dfc80", + "transactionHash": "0x529769b00763fd8481f22fec5728eb20739223fb0cd9d1af869cab9d0cce6806", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xa7889bf75b4a3a0e5bde45eb99cbd39cd26dfc83570b3772872b37afa15baf2a7393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23", + "blockHash": "0x4786aff1402e2f7c5c84e0d2e5cf0adf3d0bda16fd130da0a725ff4e16c0314d", + "blockNumber": "0x1a5", + "blockTimestamp": "0x6a5dfc84", + "transactionHash": "0xd89eba15969498132318194d6d8282c9061a7c9586e33b33f11b662476d80bef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x7393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23", + "blockHash": "0x16e77e1aff02ffbd42c19189e51ad06a4cf83b282f87da0261b0fe65bbb0debf", + "blockNumber": "0x1a8", + "blockTimestamp": "0x6a5dfc87", + "transactionHash": "0x3dc097eda19c32a57fbd7e9e486dfb4650a744436caace9a6f902c2ed68fadb0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x361754da7a2b2f85b7c35b83aa161972b2eac1c1a3ae1eb0d31cfa9961b831e07393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23", + "blockHash": "0x43165aaa57e167794af062a200d9052325e96c57096a274bc1a828784c2ba416", + "blockNumber": "0x1a9", + "blockTimestamp": "0x6a5dfc87", + "transactionHash": "0xd833fdc437e90bb5ac63ea1ccecffb7a32973e219080925f58993baa91f8b278", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561", + "0xa7889bf75b4a3a0e5bde45eb99cbd39cd26dfc83570b3772872b37afa15baf2a", + "0x361754da7a2b2f85b7c35b83aa161972b2eac1c1a3ae1eb0d31cfa9961b831e0" + ], + "data": "0x1f4c416d2e97d4e3c56bd833ac0cb5fab9a0a99487a7af801a64d5c728df8d7e", + "blockHash": "0x43165aaa57e167794af062a200d9052325e96c57096a274bc1a828784c2ba416", + "blockNumber": "0x1a9", + "blockTimestamp": "0x6a5dfc87", + "transactionHash": "0xd833fdc437e90bb5ac63ea1ccecffb7a32973e219080925f58993baa91f8b278", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0x4b5139c9af393fc5321c8a6a38e6172b2945cd5512c76146642ceb768e9f0a7a7393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23", + "blockHash": "0xa86edb5cb164f3bc57ed02210f1251a70fae3b670865fd6cdba0cea1ae17b236", + "blockNumber": "0x1aa", + "blockTimestamp": "0x6a5dfc87", + "transactionHash": "0xb74492a5220f015b14688756f41a66bd3e811390b79326f5e135c0354e115502", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906" + ], + "data": "0x371a90287dfd9fd222fcdece05e767664e807f62a5b5a038f14f704557d6500c7393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23", + "blockHash": "0x2d5e3586ffe6f8b10164d1837078d8174b3e0f2fddba2492e56202f68752845b", + "blockNumber": "0x1ac", + "blockTimestamp": "0x6a5dfc88", + "transactionHash": "0xd046258ca88b79163c959732878ab90fe12f1ce52e6122124b83ce3dc68162ea", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xf7974db1dfb2f3ba7701d684406818e39d0529e7d7abca6860130226fa30dabc", + "0x4b5139c9af393fc5321c8a6a38e6172b2945cd5512c76146642ceb768e9f0a7a", + "0x371a90287dfd9fd222fcdece05e767664e807f62a5b5a038f14f704557d6500c" + ], + "data": "0xa5f3b84a52bd25a272dcb6cc504066ad7a4c0cededada986f88ed768ae0cfd52", + "blockHash": "0x2d5e3586ffe6f8b10164d1837078d8174b3e0f2fddba2492e56202f68752845b", + "blockNumber": "0x1ac", + "blockTimestamp": "0x6a5dfc88", + "transactionHash": "0xd046258ca88b79163c959732878ab90fe12f1ce52e6122124b83ce3dc68162ea", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x1f4c416d2e97d4e3c56bd833ac0cb5fab9a0a99487a7af801a64d5c728df8d7eaf3abe583e39d7e5180f0feea7a6b476d5720d316bfd5c000e1b60e6158ad969", + "blockHash": "0x558737ccd3d2c039043aa3341288c98be96e39b2a79d8bbe6d457464b8bf8080", + "blockNumber": "0x1ae", + "blockTimestamp": "0x6a5dfc8a", + "transactionHash": "0x22a8a52df41d93f38ce025cd3bc760317d815d58fae4c800ec0717f6456906f2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x558737ccd3d2c039043aa3341288c98be96e39b2a79d8bbe6d457464b8bf8080", + "blockNumber": "0x1ae", + "blockTimestamp": "0x6a5dfc8a", + "transactionHash": "0x22a8a52df41d93f38ce025cd3bc760317d815d58fae4c800ec0717f6456906f2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xf7974db1dfb2f3ba7701d684406818e39d0529e7d7abca6860130226fa30dabc" + ], + "data": "0xa5f3b84a52bd25a272dcb6cc504066ad7a4c0cededada986f88ed768ae0cfd52fe430d03478290c96836c6ff22e39e484ed84d9fffea68624da4e9046cc6799d", + "blockHash": "0xc5c459f53775ceb91925ca360b8603a3dbc072871766aeb5ff10b7195f4bad2b", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5dfc8a", + "transactionHash": "0x4792386e9c97b886625abf18ea77b8feddb76acdd8a45c1a72a6c63b87edee1a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc5c459f53775ceb91925ca360b8603a3dbc072871766aeb5ff10b7195f4bad2b", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5dfc8a", + "transactionHash": "0x4792386e9c97b886625abf18ea77b8feddb76acdd8a45c1a72a6c63b87edee1a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xaf3abe583e39d7e5180f0feea7a6b476d5720d316bfd5c000e1b60e6158ad96903b7cffd542289fdc07d0deec557224675b2f3047adf9d70f236eaacfd384350", + "blockHash": "0x83f7f0d10251871855cdd9c214954f1c6bbe3938e3beb26db74442bbac79b56e", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5dfc8b", + "transactionHash": "0xb089bd44246563a5fa3dde6105f883c38d9c37652713abaf432b380c8f8811e3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x83f7f0d10251871855cdd9c214954f1c6bbe3938e3beb26db74442bbac79b56e", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5dfc8b", + "transactionHash": "0xb089bd44246563a5fa3dde6105f883c38d9c37652713abaf432b380c8f8811e3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x03b7cffd542289fdc07d0deec557224675b2f3047adf9d70f236eaacfd384350f1efc93d32e0e67e1618140dad395feb1eb604b327dcbff11a2d7a6951d7215c", + "blockHash": "0xca3263bfa231541321ebe178223515e9d394b6ddaa5ea3875c03abc86a5669df", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5dfc8c", + "transactionHash": "0x6a08b5b4fd01a2d5cc526a5e68477e7cb038c4b1ce7018243e37b4679f7edd6d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xca3263bfa231541321ebe178223515e9d394b6ddaa5ea3875c03abc86a5669df", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5dfc8c", + "transactionHash": "0x6a08b5b4fd01a2d5cc526a5e68477e7cb038c4b1ce7018243e37b4679f7edd6d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xf1efc93d32e0e67e1618140dad395feb1eb604b327dcbff11a2d7a6951d7215c87846904346675c14b3bc95f8177d24d478fad8035743c6a49026352b3833052", + "blockHash": "0x9475e705172f14fe09e8bdb95fbb9137f902896306c8fcf70ec848f10686ea12", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5dfc8d", + "transactionHash": "0x1b29fed1cf7d1a94c452045c79501d4194b9db3b269c06af6142e9e725812e95", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9475e705172f14fe09e8bdb95fbb9137f902896306c8fcf70ec848f10686ea12", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5dfc8d", + "transactionHash": "0x1b29fed1cf7d1a94c452045c79501d4194b9db3b269c06af6142e9e725812e95", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x87846904346675c14b3bc95f8177d24d478fad8035743c6a49026352b3833052f1db162af85bd02933f9f7c504d7a1f57691d413721d3da7d367dd258769713d", + "blockHash": "0xa993de962495d7eabf0a27f7f8e0d45857d37e39db834a5c63a10f25f714975a", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5dfc8e", + "transactionHash": "0x50940a0f5daaf3665b778d83f13ed36acd4817fc8b1ef3f6ecf120dd2ebc8a78", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa993de962495d7eabf0a27f7f8e0d45857d37e39db834a5c63a10f25f714975a", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5dfc8e", + "transactionHash": "0x50940a0f5daaf3665b778d83f13ed36acd4817fc8b1ef3f6ecf120dd2ebc8a78", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xf1db162af85bd02933f9f7c504d7a1f57691d413721d3da7d367dd258769713d4d7f543e828f8d886c15d82d1d96964a9b1d4c6bccba5aaa72c694055bbf151f", + "blockHash": "0xf537bb880e6f0c8097649770f4bb62eefb2414bbf83bd9951f572910a0b2b161", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5dfc8f", + "transactionHash": "0xa22579687e8b3f615ea71b0511c1750369dc34b266c8992e9da1fcf7468bfe5c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf537bb880e6f0c8097649770f4bb62eefb2414bbf83bd9951f572910a0b2b161", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5dfc8f", + "transactionHash": "0xa22579687e8b3f615ea71b0511c1750369dc34b266c8992e9da1fcf7468bfe5c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x4d7f543e828f8d886c15d82d1d96964a9b1d4c6bccba5aaa72c694055bbf151f285993e80d66e371d8ee30187c75e5945f851fe548aafcaf44b5b5ead2502212", + "blockHash": "0xf76a8f1afcc86cdc106f8daf7809c0a4a765f41e631f1c9284f133f6aa62bd64", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfc90", + "transactionHash": "0x7d57264d6da0cb120d2b1fbd4a00707c0f7f1dd836beb0b7a58871826d1dda69", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf76a8f1afcc86cdc106f8daf7809c0a4a765f41e631f1c9284f133f6aa62bd64", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfc90", + "transactionHash": "0x7d57264d6da0cb120d2b1fbd4a00707c0f7f1dd836beb0b7a58871826d1dda69", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x285993e80d66e371d8ee30187c75e5945f851fe548aafcaf44b5b5ead2502212e5f27ac4abbdce2644e120d9e5b1dcc0512ca6cd16ec67acc3b415a92f21f435", + "blockHash": "0x9e82189e1deb3c1f63b1361a90826074d1cdf955531c8c5b7783a6bd903707d8", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfc91", + "transactionHash": "0x4d76c64e5d64fb1d01b543136671591607cbd4e450659fe97faeb284daf3e1a1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9e82189e1deb3c1f63b1361a90826074d1cdf955531c8c5b7783a6bd903707d8", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfc91", + "transactionHash": "0x4d76c64e5d64fb1d01b543136671591607cbd4e450659fe97faeb284daf3e1a1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xe5f27ac4abbdce2644e120d9e5b1dcc0512ca6cd16ec67acc3b415a92f21f4357783261aabee445bd7cdad31f6adef031a40addbd07b26538b8f11acdf1eef06", + "blockHash": "0x7f47ad2e8795389f57b1117359eb05170a25a3d00d635108e0e8de15c29ae3e9", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfc92", + "transactionHash": "0xeff55b9710bddd1f5964f02967907bdaf4143afa5aba1efaae9d824ae12e3778", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7f47ad2e8795389f57b1117359eb05170a25a3d00d635108e0e8de15c29ae3e9", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfc92", + "transactionHash": "0xeff55b9710bddd1f5964f02967907bdaf4143afa5aba1efaae9d824ae12e3778", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x7783261aabee445bd7cdad31f6adef031a40addbd07b26538b8f11acdf1eef067e1cf71dd893b21c171aba4a5edc98064cbfb479502f1de4dcf468b9777561f7", + "blockHash": "0x0cf1e060413e5d69cf84d6b65a17d1621f7958a3c73e15f5de4c0406d12f136a", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfc93", + "transactionHash": "0x24856ef3a02e99f4f0ec07ba3dae8f8887c08cf3140a2155a5866908416a2841", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0cf1e060413e5d69cf84d6b65a17d1621f7958a3c73e15f5de4c0406d12f136a", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfc93", + "transactionHash": "0x24856ef3a02e99f4f0ec07ba3dae8f8887c08cf3140a2155a5866908416a2841", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x7e1cf71dd893b21c171aba4a5edc98064cbfb479502f1de4dcf468b9777561f74218f7650412096d989f85536ea2c2581d77b0a73f4cd24c2938fa814b1e66e3", + "blockHash": "0x6823f846783465d798f002e3e23116ff3b0abfd7ceffd6781fbf21a0138a0055", + "blockNumber": "0x1c9", + "blockTimestamp": "0x6a5dfc93", + "transactionHash": "0xde20cd46437edd0aed5fe8482819363d3ae1d10fd24a90753dd7a5c165a9f9e8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6823f846783465d798f002e3e23116ff3b0abfd7ceffd6781fbf21a0138a0055", + "blockNumber": "0x1c9", + "blockTimestamp": "0x6a5dfc93", + "transactionHash": "0xde20cd46437edd0aed5fe8482819363d3ae1d10fd24a90753dd7a5c165a9f9e8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x4218f7650412096d989f85536ea2c2581d77b0a73f4cd24c2938fa814b1e66e3729da17ae94af4d11da62dd683636a0677f8e1940f0299ba3f9a8400e48a35e7", + "blockHash": "0x00647aedc5907210ffec1fcbce7de9bf6cfbd77bae6cc2b7f93f1ae5a57adb2d", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfc94", + "transactionHash": "0x2061741a92f328d0bc23287e8ecf3ab24feb3082b9a7af50f40efbf97321ace9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x00647aedc5907210ffec1fcbce7de9bf6cfbd77bae6cc2b7f93f1ae5a57adb2d", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfc94", + "transactionHash": "0x2061741a92f328d0bc23287e8ecf3ab24feb3082b9a7af50f40efbf97321ace9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x729da17ae94af4d11da62dd683636a0677f8e1940f0299ba3f9a8400e48a35e7ede653b23412e3b15b3774a9050b92251250583abcc1e837500254567da725c0", + "blockHash": "0xb35bdf15d11fa8cd1dbc9eeb9ca54d9e4b3038ab244b124fdc0142fc5639be10", + "blockNumber": "0x1cf", + "blockTimestamp": "0x6a5dfc96", + "transactionHash": "0xf563d31116d7c3b4c02ddb0fb5188bc0d569902697e4daa24f6a98f14a47f06d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb35bdf15d11fa8cd1dbc9eeb9ca54d9e4b3038ab244b124fdc0142fc5639be10", + "blockNumber": "0x1cf", + "blockTimestamp": "0x6a5dfc96", + "transactionHash": "0xf563d31116d7c3b4c02ddb0fb5188bc0d569902697e4daa24f6a98f14a47f06d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xede653b23412e3b15b3774a9050b92251250583abcc1e837500254567da725c01802f750a8982eafb620275a8768ec274abfa565937b7860572bd143206fe6a3", + "blockHash": "0x884c053adb67b1a3c712ed9605c791bdc272299499a71ccbc8536fb27d5815fa", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfc97", + "transactionHash": "0x982d4f624786ba95b7a1bc97dc8afeb3982c9d8fff610e734c9ec64d2a216c40", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x884c053adb67b1a3c712ed9605c791bdc272299499a71ccbc8536fb27d5815fa", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfc97", + "transactionHash": "0x982d4f624786ba95b7a1bc97dc8afeb3982c9d8fff610e734c9ec64d2a216c40", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x1802f750a8982eafb620275a8768ec274abfa565937b7860572bd143206fe6a3a4a2beba496e8c1cddf1fe5f95f78ef3e72e1085256f309a38f677d26afadc9f", + "blockHash": "0x6c8860ebe59bacf02bb9a9fc81f018d1ec3759b011e507902990b541d34a8188", + "blockNumber": "0x1d5", + "blockTimestamp": "0x6a5dfc99", + "transactionHash": "0xcd9bfc900122a168817091acbf2ae6a9a0da2586a8fd5452b3901f84f586620d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6c8860ebe59bacf02bb9a9fc81f018d1ec3759b011e507902990b541d34a8188", + "blockNumber": "0x1d5", + "blockTimestamp": "0x6a5dfc99", + "transactionHash": "0xcd9bfc900122a168817091acbf2ae6a9a0da2586a8fd5452b3901f84f586620d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xa4a2beba496e8c1cddf1fe5f95f78ef3e72e1085256f309a38f677d26afadc9fdf1690b5d49c2ddc5ec7e8e9592be7d0b630cdf1af5309f3015ca776de8aba1f", + "blockHash": "0xcd873469fabab2b64f1abb95aeaa5aeae451fd9c39a16c414275110c1456a09b", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfc9a", + "transactionHash": "0x8e2111bbfe283aced2c115b82dcead877e7a4e46eacce2364512a8987cf4d9f8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcd873469fabab2b64f1abb95aeaa5aeae451fd9c39a16c414275110c1456a09b", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfc9a", + "transactionHash": "0x8e2111bbfe283aced2c115b82dcead877e7a4e46eacce2364512a8987cf4d9f8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xdf1690b5d49c2ddc5ec7e8e9592be7d0b630cdf1af5309f3015ca776de8aba1f1eef6b11036c2408533aeb4815e8deedb8b33c091fca458e8439d642dc5f9e9f", + "blockHash": "0xcd27ffec41be0bdb5afe163ada15344f7f9f89bcf2782c13cdc3ac601ee1605a", + "blockNumber": "0x1db", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x8fd0d8f2d304709204df5ba87058dd8ac9b9b3bfa73210324da52e94a24ec518", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcd27ffec41be0bdb5afe163ada15344f7f9f89bcf2782c13cdc3ac601ee1605a", + "blockNumber": "0x1db", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x8fd0d8f2d304709204df5ba87058dd8ac9b9b3bfa73210324da52e94a24ec518", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x1eef6b11036c2408533aeb4815e8deedb8b33c091fca458e8439d642dc5f9e9f22d0eec1b8178bc2f5c50d1bf3ef712a5f53e82869c4fe965317f3d223da622b", + "blockHash": "0xc65625b1d39ecab97ba8f8642e6ea1a66aca60bda6c896de288ba4b9abd9e77b", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x1ddc70f8be9ddabf6a608166826aacdd5b62b4af6650e18ced6305339bb3f88e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc65625b1d39ecab97ba8f8642e6ea1a66aca60bda6c896de288ba4b9abd9e77b", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x1ddc70f8be9ddabf6a608166826aacdd5b62b4af6650e18ced6305339bb3f88e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x22d0eec1b8178bc2f5c50d1bf3ef712a5f53e82869c4fe965317f3d223da622b9bf2f85b64a5f6e93abede532e44746cfb881ce323875452ec3b7c6417d3cccb", + "blockHash": "0xfb58449f802a6f8a39ffe26d3a7d761e06fc66f96e558c167d4f68cae59e86fb", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x32797bcb64e92210571f30fba41460c4ca60794a850c85d05890d1d4a3bdd754", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfb58449f802a6f8a39ffe26d3a7d761e06fc66f96e558c167d4f68cae59e86fb", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfc9c", + "transactionHash": "0x32797bcb64e92210571f30fba41460c4ca60794a850c85d05890d1d4a3bdd754", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x9bf2f85b64a5f6e93abede532e44746cfb881ce323875452ec3b7c6417d3cccb38b8642360da4b9ade3f3899f51c5c43c70f31c46b373830639915591d48cbba", + "blockHash": "0x0a63a20a8b707558dc9ebff7c253be6de85c1bdd7a2ca50ef34e382c56486773", + "blockNumber": "0x1de", + "blockTimestamp": "0x6a5dfc9d", + "transactionHash": "0x52b193641fdd5f0e266cbded3a178727ef2a611558296817159cac350585e053", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0a63a20a8b707558dc9ebff7c253be6de85c1bdd7a2ca50ef34e382c56486773", + "blockNumber": "0x1de", + "blockTimestamp": "0x6a5dfc9d", + "transactionHash": "0x52b193641fdd5f0e266cbded3a178727ef2a611558296817159cac350585e053", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x38b8642360da4b9ade3f3899f51c5c43c70f31c46b373830639915591d48cbba5054bbcfc3ee68ad1493a8c9e42abee8aeaf66f0aed7646216d9df7d7d573889", + "blockHash": "0x1a4ad558e855ece864cc4743b18d3a0fb980be3fb874eec13a31e03441cd020b", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfc9d", + "transactionHash": "0x43c804db320c5f79419e268813cc78696e4855629c2e26d5fdfb8e007b34db45", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1a4ad558e855ece864cc4743b18d3a0fb980be3fb874eec13a31e03441cd020b", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfc9d", + "transactionHash": "0x43c804db320c5f79419e268813cc78696e4855629c2e26d5fdfb8e007b34db45", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x5054bbcfc3ee68ad1493a8c9e42abee8aeaf66f0aed7646216d9df7d7d57388905fbfce9f2a3cf1dee8888f0efd3d92214e1247d24ffb83dae6113608bde454d", + "blockHash": "0x8a9fd43612c60d46ed35580990d8721fbb9bfc40b896e45a5bf31936d40f68ee", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfc9e", + "transactionHash": "0xb537c2b3cccce7caad6e19ef21541316ced10b93a690a0519964cd02f3014f02", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8a9fd43612c60d46ed35580990d8721fbb9bfc40b896e45a5bf31936d40f68ee", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfc9e", + "transactionHash": "0xb537c2b3cccce7caad6e19ef21541316ced10b93a690a0519964cd02f3014f02", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x05fbfce9f2a3cf1dee8888f0efd3d92214e1247d24ffb83dae6113608bde454d61c6cbf3f7e1ecdbbb7cd3a6f8723b448229cdfa7424e64d9e2d817a50a64279", + "blockHash": "0xe1ce62744a1a2411d79a0b2272aedcd581e8fb0c0115cf08f7c8129760955ed9", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfc9f", + "transactionHash": "0xa735d3684fa7cfd5b4c02ea08e47c675fe63b783e21f535f1e1f42cc9d69f10c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe1ce62744a1a2411d79a0b2272aedcd581e8fb0c0115cf08f7c8129760955ed9", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfc9f", + "transactionHash": "0xa735d3684fa7cfd5b4c02ea08e47c675fe63b783e21f535f1e1f42cc9d69f10c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x61c6cbf3f7e1ecdbbb7cd3a6f8723b448229cdfa7424e64d9e2d817a50a642795cb4c28650e1e4af902c38fe212aa330280d4d7bbd0f5c8033c8f45544585abd", + "blockHash": "0x03e8050262ceef310b80f002d2a379e4556fafe335543a44cea559f14c5c7f4b", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfca0", + "transactionHash": "0xe31b3b8eaa2eb293bead1c20fba588343712c9740e522b835db7a381b9ad2f5d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x03e8050262ceef310b80f002d2a379e4556fafe335543a44cea559f14c5c7f4b", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfca0", + "transactionHash": "0xe31b3b8eaa2eb293bead1c20fba588343712c9740e522b835db7a381b9ad2f5d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x5cb4c28650e1e4af902c38fe212aa330280d4d7bbd0f5c8033c8f45544585abd6e8651a698e9c00bf2e6cb78052e032ff93a73837bd0e1c516baeec1ae625744", + "blockHash": "0x3ae790d5ec34808143e353b4d176492094d1d084f06dc46cd69e2bb987a4c053", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfca1", + "transactionHash": "0x16738357c0241a8472bc84e1d8e5b48f6be4bf23a5980f3c9348080d58a73d12", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3ae790d5ec34808143e353b4d176492094d1d084f06dc46cd69e2bb987a4c053", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfca1", + "transactionHash": "0x16738357c0241a8472bc84e1d8e5b48f6be4bf23a5980f3c9348080d58a73d12", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x6e8651a698e9c00bf2e6cb78052e032ff93a73837bd0e1c516baeec1ae6257448ca82fd0719d56c0ae713cd0f74a48bffecc469df08a87f61181e5deac83d352", + "blockHash": "0xbbc9843a12e692fec26e8ae595552d09b3ab2d585aa941b4c5a75aab20c672cb", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfca2", + "transactionHash": "0x92ee80bdd59d74bf284a9b8c712de8bbac21a417a25c31ec65b8a85b9acb1d15", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbbc9843a12e692fec26e8ae595552d09b3ab2d585aa941b4c5a75aab20c672cb", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfca2", + "transactionHash": "0x92ee80bdd59d74bf284a9b8c712de8bbac21a417a25c31ec65b8a85b9acb1d15", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x8ca82fd0719d56c0ae713cd0f74a48bffecc469df08a87f61181e5deac83d3527200eb8c73dc2a4134fa27451b215671d6af6b38b049f33728f272c55a89363a", + "blockHash": "0x620aea5200584ffd8d299b0e0741968a16031f6dd2d16e8299d36fd194650f8d", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dff6f", + "transactionHash": "0x789c45475c7cdf4e3ff4af78a90b473249a44314e8b2cd9fbdb82d23311ba65f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x620aea5200584ffd8d299b0e0741968a16031f6dd2d16e8299d36fd194650f8d", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dff6f", + "transactionHash": "0x789c45475c7cdf4e3ff4af78a90b473249a44314e8b2cd9fbdb82d23311ba65f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x7200eb8c73dc2a4134fa27451b215671d6af6b38b049f33728f272c55a89363a93ab97ca5316212af47671b44b373cde099d29aa056fe2b15b850bf8fb41e0ec", + "blockHash": "0x68a0e8965b6a320ea3548a008667151f1edea3086c239d518c127f21d7286dba", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dff6f", + "transactionHash": "0x0cc8d6ada94eedf2c42d94799600f8e61c153cadaca154a77b38c977ed5cd6d7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x68a0e8965b6a320ea3548a008667151f1edea3086c239d518c127f21d7286dba", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dff6f", + "transactionHash": "0x0cc8d6ada94eedf2c42d94799600f8e61c153cadaca154a77b38c977ed5cd6d7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x93ab97ca5316212af47671b44b373cde099d29aa056fe2b15b850bf8fb41e0ec77209013fcf8dc18e701c5fff4315ee266bdd1d61a2e3cff2cdcb319b6667539", + "blockHash": "0x76f659b19db0ea64ee5d9e5ff467b63cf95b0f3b46b1becb0f076c07559a7acd", + "blockNumber": "0x1f3", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0xe3b47b209681669e54e40963357c451ff8f8e58b24aa61c9aa6c6a7ca3665b40", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x76f659b19db0ea64ee5d9e5ff467b63cf95b0f3b46b1becb0f076c07559a7acd", + "blockNumber": "0x1f3", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0xe3b47b209681669e54e40963357c451ff8f8e58b24aa61c9aa6c6a7ca3665b40", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x77209013fcf8dc18e701c5fff4315ee266bdd1d61a2e3cff2cdcb319b66675394025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc", + "blockHash": "0x26cec78ef4c93469a4f7d8ca6d364215d47a2bcb1d34721312cb8d89f1270ff2", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0x7316a867fa57bb1fd475b993928a8c5635016f5b063f51394e5f1010c63e7b39", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x26cec78ef4c93469a4f7d8ca6d364215d47a2bcb1d34721312cb8d89f1270ff2", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0x7316a867fa57bb1fd475b993928a8c5635016f5b063f51394e5f1010c63e7b39", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbcd1f1580c93beaf3f52a1754e706369771836bdb130f03adc741b89ce047bc6b6", + "blockHash": "0x3d630172f033f8235453a30cffb210d5c93cf532ea22dddebf78cce3ac80293e", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0xadb6d50d9ab122c96e61c595714229cd489c184b552bf96087be8d163badc085", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3d630172f033f8235453a30cffb210d5c93cf532ea22dddebf78cce3ac80293e", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dff70", + "transactionHash": "0xadb6d50d9ab122c96e61c595714229cd489c184b552bf96087be8d163badc085", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xd1f1580c93beaf3f52a1754e706369771836bdb130f03adc741b89ce047bc6b6693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5", + "blockHash": "0xf01738388a8853e86dd77cda4b3f2029c97be757294e17e084a4c2d314ebd67c", + "blockNumber": "0x1f6", + "blockTimestamp": "0x6a5dff71", + "transactionHash": "0xd764cf0b751cbb2420dcccd54dbc253167da0b1899e21d560003cb76b4ce79e7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf01738388a8853e86dd77cda4b3f2029c97be757294e17e084a4c2d314ebd67c", + "blockNumber": "0x1f6", + "blockTimestamp": "0x6a5dff71", + "transactionHash": "0xd764cf0b751cbb2420dcccd54dbc253167da0b1899e21d560003cb76b4ce79e7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b59fe15557addf83fee2888854cdf118e3e597a4147c353ef042c5576a68f835ec", + "blockHash": "0xe7846344afdb489cb40b6ca30a967cd95659061694c293ccd217bb72b3badfeb", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0xdf8cc7e8d7b9b601026e83ba5719cc43ea3d0fd99221472c40412fa1b25dd5c0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe7846344afdb489cb40b6ca30a967cd95659061694c293ccd217bb72b3badfeb", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0xdf8cc7e8d7b9b601026e83ba5719cc43ea3d0fd99221472c40412fa1b25dd5c0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x9fe15557addf83fee2888854cdf118e3e597a4147c353ef042c5576a68f835ec2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bdd", + "blockHash": "0x2186b49b0547df8a330027200072c1c045e96d24fed4a831d3d96fb6cf4ffdc2", + "blockNumber": "0x1fc", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0xb057b06271531a29153a0c9309ae733905f482c76c6922e8e17492dfdf1b1c53", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2186b49b0547df8a330027200072c1c045e96d24fed4a831d3d96fb6cf4ffdc2", + "blockNumber": "0x1fc", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0xb057b06271531a29153a0c9309ae733905f482c76c6922e8e17492dfdf1b1c53", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bdda31ea3e0babff1230d446c40ab79f4e5bdab2f2a05b0c86638d42d9de8a40087", + "blockHash": "0x76202cc045c4b00ff9045d7862e9800ad322d0d231a2697d4b00becf47b999f6", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0x7ff7c1a29de51df80a9a13f9f30c14f206f54fac2d6316c3463a8937b0086d14", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x76202cc045c4b00ff9045d7862e9800ad322d0d231a2697d4b00becf47b999f6", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dff73", + "transactionHash": "0x7ff7c1a29de51df80a9a13f9f30c14f206f54fac2d6316c3463a8937b0086d14", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xa31ea3e0babff1230d446c40ab79f4e5bdab2f2a05b0c86638d42d9de8a400870acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66", + "blockHash": "0xe3e0983bc8be45cc54215f83821e8c97c8201229a391921413e13ab9619ce988", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dff74", + "transactionHash": "0xd2cf70ed644743b8a8d0300b3ef6b5da19328b4a867384e9e7506632ba2ae008", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe3e0983bc8be45cc54215f83821e8c97c8201229a391921413e13ab9619ce988", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dff74", + "transactionHash": "0xd2cf70ed644743b8a8d0300b3ef6b5da19328b4a867384e9e7506632ba2ae008", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x0acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66ebb02da26d129a719654c2cfc434e40c0dd26c89164cba437e081005e4adbe78", + "blockHash": "0x9d167db154db081e4b55cdd0a3e04b6284dc5ee356fcd846e8d5f138e5b6107a", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dff76", + "transactionHash": "0x68b6ff87d8b855d27ab9766eb50dd4421b92505154b2da1bb8b1f272fb417a3c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9d167db154db081e4b55cdd0a3e04b6284dc5ee356fcd846e8d5f138e5b6107a", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dff76", + "transactionHash": "0x68b6ff87d8b855d27ab9766eb50dd4421b92505154b2da1bb8b1f272fb417a3c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xebb02da26d129a719654c2cfc434e40c0dd26c89164cba437e081005e4adbe781beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590", + "blockHash": "0x137107d47b1389a97ed7b7dda868c5e012ad480d68b88f43443142bdcf8a297a", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dff76", + "transactionHash": "0xb329c88e517c438a04a017ad4e2a25cb02157e721516e297a94b019755f9c959", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x137107d47b1389a97ed7b7dda868c5e012ad480d68b88f43443142bdcf8a297a", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dff76", + "transactionHash": "0xb329c88e517c438a04a017ad4e2a25cb02157e721516e297a94b019755f9c959", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x1beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e59085bb65acbf38ec1074c39bfd020b747fb3704116154d6199c40a95cd19491e95", + "blockHash": "0x008ca124c81b824a3d24d9aea933b95e69f292d290bfcccf34c58220dcf8e9d5", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x504213d3e2645ced008b9a769098078a32bb9d4aecfcbe527d97427c5a2a5126", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x008ca124c81b824a3d24d9aea933b95e69f292d290bfcccf34c58220dcf8e9d5", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x504213d3e2645ced008b9a769098078a32bb9d4aecfcbe527d97427c5a2a5126", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x85bb65acbf38ec1074c39bfd020b747fb3704116154d6199c40a95cd19491e95f537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca579", + "blockHash": "0x9f95b02841cf568c046167f9695cb88ecb76185fa25f9f7d191043b47aeed501", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x5becc9dab9db6b09cd2fce3d442f795d4aec77e9427df04e3a4e8832ede7bbb9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9f95b02841cf568c046167f9695cb88ecb76185fa25f9f7d191043b47aeed501", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x5becc9dab9db6b09cd2fce3d442f795d4aec77e9427df04e3a4e8832ede7bbb9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xf537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca579f18ad7a3ea272656b27cf8413441f2ca1d88f3dcf85dcf5d9310c73ec6d2948d", + "blockHash": "0xa71361b7da2bae570eecf9955220c2c94626b3284c0cf3ddeb6a8de65bf3705f", + "blockNumber": "0x20b", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x8273fc6d580528832d5c8979aff9a0c3e91358f4b10eb5418eece1cf3d8a4261", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa71361b7da2bae570eecf9955220c2c94626b3284c0cf3ddeb6a8de65bf3705f", + "blockNumber": "0x20b", + "blockTimestamp": "0x6a5dff78", + "transactionHash": "0x8273fc6d580528832d5c8979aff9a0c3e91358f4b10eb5418eece1cf3d8a4261", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xf18ad7a3ea272656b27cf8413441f2ca1d88f3dcf85dcf5d9310c73ec6d2948d76b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c5", + "blockHash": "0xdbbbdff9e1c13d21087bf2580569372a0ae11f0a6794a34676b204410b92f368", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dff79", + "transactionHash": "0x08febcce96587b74481afb1a905a2f44916ad1d7d5a517394804a789387bbbff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdbbbdff9e1c13d21087bf2580569372a0ae11f0a6794a34676b204410b92f368", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dff79", + "transactionHash": "0x08febcce96587b74481afb1a905a2f44916ad1d7d5a517394804a789387bbbff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x76b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c5094a4fbc5e94c11b7225817fedd33750b94e648d197b215a9f21f965bbbef972", + "blockHash": "0x2f5b1215d072d1f87b358ce4ee13515fa08a1a68a3d30338fee0ae1a5bd5a065", + "blockNumber": "0x211", + "blockTimestamp": "0x6a5dff7b", + "transactionHash": "0x6246f6c93a4a677c7c98ed79aafa34a160f88263b09ec9cf017fa517bea7ff79", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2f5b1215d072d1f87b358ce4ee13515fa08a1a68a3d30338fee0ae1a5bd5a065", + "blockNumber": "0x211", + "blockTimestamp": "0x6a5dff7b", + "transactionHash": "0x6246f6c93a4a677c7c98ed79aafa34a160f88263b09ec9cf017fa517bea7ff79", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x094a4fbc5e94c11b7225817fedd33750b94e648d197b215a9f21f965bbbef9723bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb", + "blockHash": "0xc4307b335cf350475a1395a29af46e24d444aaf1dad717b1c3c34cf72e6799ec", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dff7b", + "transactionHash": "0x63c93a48abd3d53e9079b45f680e3c844e75e702654710cffc218050e476564e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc4307b335cf350475a1395a29af46e24d444aaf1dad717b1c3c34cf72e6799ec", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dff7b", + "transactionHash": "0x63c93a48abd3d53e9079b45f680e3c844e75e702654710cffc218050e476564e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x3bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb7dbac8e3cd61789f0b79d1c92bbafd290229e1de1a5907b89b7e4cc200d7cba4", + "blockHash": "0x4616f8ed43d1864722ecb68c957f5f53f72c16a6091882079358831ad22b1311", + "blockNumber": "0x217", + "blockTimestamp": "0x6a5dff7d", + "transactionHash": "0xbcc13a8bcccac4d86026c627d3e88fbd738c8fb12e7793976a98850273c272e3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4616f8ed43d1864722ecb68c957f5f53f72c16a6091882079358831ad22b1311", + "blockNumber": "0x217", + "blockTimestamp": "0x6a5dff7d", + "transactionHash": "0xbcc13a8bcccac4d86026c627d3e88fbd738c8fb12e7793976a98850273c272e3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0x7dbac8e3cd61789f0b79d1c92bbafd290229e1de1a5907b89b7e4cc200d7cba4c6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab", + "blockHash": "0xee381ed46a636fe1e753cc8e2489d9b73b8093c5f4093789e59c466379a95c92", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dff7d", + "transactionHash": "0x73b87389afabf524bbf041027487b44b337fe9a2f1397495899b27459f2d6efb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xee381ed46a636fe1e753cc8e2489d9b73b8093c5f4093789e59c466379a95c92", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dff7d", + "transactionHash": "0x73b87389afabf524bbf041027487b44b337fe9a2f1397495899b27459f2d6efb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561" + ], + "data": "0xc6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3a8b5e66d5abb2359222b44640966032d1d69ff0eff4aba0a7f47a6605b60331", + "blockNumber": "0x21d", + "blockTimestamp": "0x6a5dff7f", + "transactionHash": "0xc066ab8ce3d2bc7e1e7025df60be3ab22d1899cccbc2d503d081c0a1e7843b6b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3a8b5e66d5abb2359222b44640966032d1d69ff0eff4aba0a7f47a6605b60331", + "blockNumber": "0x21d", + "blockTimestamp": "0x6a5dff7f", + "transactionHash": "0xc066ab8ce3d2bc7e1e7025df60be3ab22d1899cccbc2d503d081c0a1e7843b6b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561", + "0x0000000000000000000000006b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7" + ], + "data": "0x", + "blockHash": "0xfd34c86a814bdc57751f76b1e18b583814c7657f3b240a9315c069981ee213d6", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dff7f", + "transactionHash": "0xfef5950894e6ce04634081f1ab27bbf7ca16050b811c88a31f3ee5e707a8d8a1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000002490e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfd34c86a814bdc57751f76b1e18b583814c7657f3b240a9315c069981ee213d6", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dff7f", + "transactionHash": "0xfef5950894e6ce04634081f1ab27bbf7ca16050b811c88a31f3ee5e707a8d8a1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xde7c61c9c3bf0570e7b0da84a595c7093cebf9dafdf43cfad59f6949719711820000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x07f94b613e1722534c39fefe2fe4e9cbc1edcbaab4ad3d4a7b6538f13d9087fa", + "blockNumber": "0x21f", + "blockTimestamp": "0x6a5dff80", + "transactionHash": "0xf8211240b9fbdb03c0ff2b322935d815206491179c9581f5bfa1e4d822526b05", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0xf323408e1bab99166ada150f17c4ece1fc95a019541f43cc019b0e3b55e7011792ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0x9a7beb51741327ee6dc7c2cf2fcce3c138d4d3b79dc02b5b87ec171d62614c01", + "blockNumber": "0x220", + "blockTimestamp": "0x6a5dff81", + "transactionHash": "0xf86a29dc28dc2379d839f3b0350e12a5471aab362569f9cb90ac56f60472b46f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840", + "0xde7c61c9c3bf0570e7b0da84a595c7093cebf9dafdf43cfad59f694971971182", + "0xf323408e1bab99166ada150f17c4ece1fc95a019541f43cc019b0e3b55e70117" + ], + "data": "0x9fc2669fc2a45f3e201f37b99e1d9c369ae8033a68fc862b70a083aae693edb6", + "blockHash": "0x9a7beb51741327ee6dc7c2cf2fcce3c138d4d3b79dc02b5b87ec171d62614c01", + "blockNumber": "0x220", + "blockTimestamp": "0x6a5dff81", + "transactionHash": "0xf86a29dc28dc2379d839f3b0350e12a5471aab362569f9cb90ac56f60472b46f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x260116ebf7233a663e2723e7f47212fb6b6a59939e29000f6e84db38f1e4f18a693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5", + "blockHash": "0x63bc94f7aeeb45c7a066df4d4ed9ac0e35f647b8cb2b338fbaf890df8de65d4d", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dff81", + "transactionHash": "0x6609b10a0163eb1ae2f2d72e96c1ca42d1f7750f33a98495a52376993022e624", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x63bc94f7aeeb45c7a066df4d4ed9ac0e35f647b8cb2b338fbaf890df8de65d4d", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dff81", + "transactionHash": "0x6609b10a0163eb1ae2f2d72e96c1ca42d1f7750f33a98495a52376993022e624", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x4e64e5e09631108bac17cafe63be75423e16140dde6a5e8b1bf6842e35831c68a5ccd9aed509f22289dad739a70c64bac45282d709182320875523aae28f1bae", + "blockHash": "0x8f9457dc50fb76cf397db54505f0bb90e15301052099f6ccc594136b29dfa272", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dff82", + "transactionHash": "0xe943cc812f6efb3542f475b1fb5cdd662b83a488d72d52d021af6d1652e241ea", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8f9457dc50fb76cf397db54505f0bb90e15301052099f6ccc594136b29dfa272", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dff82", + "transactionHash": "0xe943cc812f6efb3542f475b1fb5cdd662b83a488d72d52d021af6d1652e241ea", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0xa5ccd9aed509f22289dad739a70c64bac45282d709182320875523aae28f1bae2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bdd", + "blockHash": "0x16694748d4ce682b87ae025cfdf9565dc407d669b38740bf3b659ce2f95907d3", + "blockNumber": "0x223", + "blockTimestamp": "0x6a5dff83", + "transactionHash": "0x1dbf3bfe994f12b1189672c4e7f4c1df8f1788a62fd5360ee542202222fb0bfb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x16694748d4ce682b87ae025cfdf9565dc407d669b38740bf3b659ce2f95907d3", + "blockNumber": "0x223", + "blockTimestamp": "0x6a5dff83", + "transactionHash": "0x1dbf3bfe994f12b1189672c4e7f4c1df8f1788a62fd5360ee542202222fb0bfb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x9973d565588cd9cdf8da26c0b9d15bfb3b62293ecb1f7dceef2556cffff95376d2cb250d317c20301f19dba9aa752c9c0aea7f939d00e825539dc2bc589b799c", + "blockHash": "0x018c6c46cef9f1da48c057b9cf1c20d06a5518b59d538f9d20d6f080ae893d5d", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dff83", + "transactionHash": "0xfc5d94e3267d18585a526d89ee38a6688f6d3621802d3acb1ad14ae03f270a2d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x018c6c46cef9f1da48c057b9cf1c20d06a5518b59d538f9d20d6f080ae893d5d", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dff83", + "transactionHash": "0xfc5d94e3267d18585a526d89ee38a6688f6d3621802d3acb1ad14ae03f270a2d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0xd2cb250d317c20301f19dba9aa752c9c0aea7f939d00e825539dc2bc589b799c0acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66", + "blockHash": "0x57ecde6521c35c0118b48e798045e3762bed71662a5523b8c3051a5f3d5d7bb9", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dff84", + "transactionHash": "0xa76f03cc6be2b2ed6d6bae196d47e335b83f966a8b72aa5d466cfc2aa9614b4c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x57ecde6521c35c0118b48e798045e3762bed71662a5523b8c3051a5f3d5d7bb9", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dff84", + "transactionHash": "0xa76f03cc6be2b2ed6d6bae196d47e335b83f966a8b72aa5d466cfc2aa9614b4c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x3b7165fc2430e21217a984b04abeae65927a94d11e13046a3bfa34a9fcb24441f254da09d7ffb13ab8a5e703b529a371bf045829f5b0503f52c0dc6920da715a", + "blockHash": "0xa9a10ceeb51b0ad6360a85fbb090022ad3a8bcb53f8bf2e9a9c78e46e1c759a6", + "blockNumber": "0x226", + "blockTimestamp": "0x6a5dff84", + "transactionHash": "0x60cfc15745f95bf84eee42c344a54ea6000c86328a071537f41e3c590fd322bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa9a10ceeb51b0ad6360a85fbb090022ad3a8bcb53f8bf2e9a9c78e46e1c759a6", + "blockNumber": "0x226", + "blockTimestamp": "0x6a5dff84", + "transactionHash": "0x60cfc15745f95bf84eee42c344a54ea6000c86328a071537f41e3c590fd322bb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0xf254da09d7ffb13ab8a5e703b529a371bf045829f5b0503f52c0dc6920da715a1beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590", + "blockHash": "0x977eef7c0e90fd0d6a8cc5c731456a2e839297ccaae2624474694e2269549aa6", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dff85", + "transactionHash": "0x170d51f8722e03fd21b8812fc327442a0dca06c3ea6925f16dc01a1b7256be2e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x977eef7c0e90fd0d6a8cc5c731456a2e839297ccaae2624474694e2269549aa6", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dff85", + "transactionHash": "0x170d51f8722e03fd21b8812fc327442a0dca06c3ea6925f16dc01a1b7256be2e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0xcea769dab50418c87dc76b935d8fbca89bc682ba3eb32e25ea00aee868abae57144f52e8c3ee31a01f6baf708548ad91d7be09b790c19f9607859b2090d9d31e", + "blockHash": "0x9ca9b57211b7ac2da7e015992fca45dbac48adff476340b7db7f5964b43cce9b", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dff85", + "transactionHash": "0x092bed0d2d8e3ae77444b34bd72856aa9cc0407edba094d4542deb6ee0a60c5c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9ca9b57211b7ac2da7e015992fca45dbac48adff476340b7db7f5964b43cce9b", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dff85", + "transactionHash": "0x092bed0d2d8e3ae77444b34bd72856aa9cc0407edba094d4542deb6ee0a60c5c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x144f52e8c3ee31a01f6baf708548ad91d7be09b790c19f9607859b2090d9d31ef537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca579", + "blockHash": "0x22262e70f92e75df42337d8f10da5b5ea93dd0b0270b0e19fa6536b38a3a06cc", + "blockNumber": "0x229", + "blockTimestamp": "0x6a5dff86", + "transactionHash": "0x16fb0f3a9f0cc4b9ab0d58c9a192753b636dc4f6023ddd42747f042b8b3d362a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x22262e70f92e75df42337d8f10da5b5ea93dd0b0270b0e19fa6536b38a3a06cc", + "blockNumber": "0x229", + "blockTimestamp": "0x6a5dff86", + "transactionHash": "0x16fb0f3a9f0cc4b9ab0d58c9a192753b636dc4f6023ddd42747f042b8b3d362a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x23cdfff3d336303f6e7eece65c000a9f44ae514e2f8326bd3c7e01ac6fdf80819a641c5e401bfcfb6671fdef0ca942b6dc92d1009fe18a668ca20611fbd3cdd4", + "blockHash": "0x4d378636705d883e706ceba435ff8ead6c0c2b94175d7952b420197da782eff2", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dff86", + "transactionHash": "0xa3a3d9330950e42049b7da4b5c2b910aec71203ba807c68362a99804747ea309", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4d378636705d883e706ceba435ff8ead6c0c2b94175d7952b420197da782eff2", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dff86", + "transactionHash": "0xa3a3d9330950e42049b7da4b5c2b910aec71203ba807c68362a99804747ea309", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x9a641c5e401bfcfb6671fdef0ca942b6dc92d1009fe18a668ca20611fbd3cdd476b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c5", + "blockHash": "0x7e82ba8a04a8b4b5619fec13d559343e6d055b49aa1ff6317301947afb042c7a", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dff87", + "transactionHash": "0xf035dbb117509dc7fa5219484134edce3c0643b28cd2ed6d05ff2a0f5fe27077", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7e82ba8a04a8b4b5619fec13d559343e6d055b49aa1ff6317301947afb042c7a", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dff87", + "transactionHash": "0xf035dbb117509dc7fa5219484134edce3c0643b28cd2ed6d05ff2a0f5fe27077", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x83d6bceda58c4b71984ce787fd83b1004a9bb15adb2fac8c8ca85e8ee0030bbb45a14b533f220ee97ef7091b2ce68ddc4f97c781b6869a1bbe8b225dd00a4bfc", + "blockHash": "0x0783c30fbb0fbb6580f866f02e534b738e3cfc73d49a68405f0ce9aa359eb753", + "blockNumber": "0x22c", + "blockTimestamp": "0x6a5dff87", + "transactionHash": "0xb566f2d7781663a01d7ed14c7b906b7999a2f8f91560b74f33ee35367cb566a3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0783c30fbb0fbb6580f866f02e534b738e3cfc73d49a68405f0ce9aa359eb753", + "blockNumber": "0x22c", + "blockTimestamp": "0x6a5dff87", + "transactionHash": "0xb566f2d7781663a01d7ed14c7b906b7999a2f8f91560b74f33ee35367cb566a3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x45a14b533f220ee97ef7091b2ce68ddc4f97c781b6869a1bbe8b225dd00a4bfc3bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb", + "blockHash": "0xd7ba784f77232c7fa5df8cb9d9d59529e37e310b36b83314205e24f1f7bd5e87", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dff88", + "transactionHash": "0xc25e4dd88ecabb620d9272b6ad3db2e77c7d0e8ef039296af8a84162e31b09db", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd7ba784f77232c7fa5df8cb9d9d59529e37e310b36b83314205e24f1f7bd5e87", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dff88", + "transactionHash": "0xc25e4dd88ecabb620d9272b6ad3db2e77c7d0e8ef039296af8a84162e31b09db", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x4c09f542d7ef0674e9a24a747b312c9cf0e273ddbf132d6dc9d6b59f284298c068cd35acb3d6a699225e69c92da70a35541a726261ce9baa09d5caee807c5401", + "blockHash": "0xd562718805096db7bfa4884abc0093965a0fb456997d365000c6bd50de3e5825", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dff88", + "transactionHash": "0xf7a5bd5610325a1e65711ec8b9b64b55c29abe841da22cc7077a48ddb82c5080", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd562718805096db7bfa4884abc0093965a0fb456997d365000c6bd50de3e5825", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dff88", + "transactionHash": "0xf7a5bd5610325a1e65711ec8b9b64b55c29abe841da22cc7077a48ddb82c5080", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x68cd35acb3d6a699225e69c92da70a35541a726261ce9baa09d5caee807c5401c6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab", + "blockHash": "0xd46d701e59ff0cd7426bc2a5c82d5373757936677cc9ce082fdbdd50b43552af", + "blockNumber": "0x22f", + "blockTimestamp": "0x6a5dff89", + "transactionHash": "0x248dfc2494f65da7063d6e8a221876c1fa1f356972dd8f5b4d4af2b8969a2f0d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd46d701e59ff0cd7426bc2a5c82d5373757936677cc9ce082fdbdd50b43552af", + "blockNumber": "0x22f", + "blockTimestamp": "0x6a5dff89", + "transactionHash": "0x248dfc2494f65da7063d6e8a221876c1fa1f356972dd8f5b4d4af2b8969a2f0d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840" + ], + "data": "0x12ef47d0bae6c05db6d6625d124197842148cef9dbd10f07ed6c4577357c7ce892ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0x7d60ca093159f8302b1d28b0d539ba445518d44b1dc5d124525ae312e2933584", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dff89", + "transactionHash": "0x21f3a1cf163df3eb2c6208c4530dc8a135dff85e5deb291364b6a00aae720561", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7d60ca093159f8302b1d28b0d539ba445518d44b1dc5d124525ae312e2933584", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dff89", + "transactionHash": "0x21f3a1cf163df3eb2c6208c4530dc8a135dff85e5deb291364b6a00aae720561", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840", + "0x0000000000000000000000008e4990ef899d2fb0cac52be4d3813a0543ad4431" + ], + "data": "0x", + "blockHash": "0x7bbca5e481c86aef4312d816946ab9e155edc050f61fd8574167550db87b382b", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dff8a", + "transactionHash": "0x72d5ca22791c1b9b9364f076a470350fc0dbbeda346d860facb629608946a566", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000025da5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7bbca5e481c86aef4312d816946ab9e155edc050f61fd8574167550db87b382b", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dff8a", + "transactionHash": "0x72d5ca22791c1b9b9364f076a470350fc0dbbeda346d860facb629608946a566", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f92ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0xe7ae01b0b73ced6bfbd6df2a6d2d1d99003ad0d2c9a2b131ea960aab6811703f", + "blockNumber": "0x232", + "blockTimestamp": "0x6a5dff8a", + "transactionHash": "0x2c1c69c1d2291d1056b895be9cf6867b477684e9d3f4997d63c9267b488dd511", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea980000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x70293ef5241bf3706d0f9b469f8e7ec1152c82f0918f524b5131d2a24451f03d", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dff8b", + "transactionHash": "0xce1ac441986449b4f3437a410d202a7b39928169b0393dfd45aa4721e58b2b96", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab", + "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f", + "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea98" + ], + "data": "0x54096d0c27c37e34edb153cb201c6355ca2795f64ac479d63720b155683143dd", + "blockHash": "0x70293ef5241bf3706d0f9b469f8e7ec1152c82f0918f524b5131d2a24451f03d", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dff8b", + "transactionHash": "0xce1ac441986449b4f3437a410d202a7b39928169b0393dfd45aa4721e58b2b96", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xdb5ee7d772a5e433360f3c5b7cb69fde1d986ef499874b157f5daafc73c94ae5803566ac0efad943268cec89179e8eaff8f44cfef6a0a979e4b4f28efbec24b3", + "blockHash": "0x7e2b7f34b6be6ab51477a120106e32b062ff64f933d643e17e1622388281d470", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dff8b", + "transactionHash": "0xa2d606fb9922f065b2092479861d65a18f0846ffab8c58ee4439f5ad1c1ffc90", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7e2b7f34b6be6ab51477a120106e32b062ff64f933d643e17e1622388281d470", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dff8b", + "transactionHash": "0xa2d606fb9922f065b2092479861d65a18f0846ffab8c58ee4439f5ad1c1ffc90", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x803566ac0efad943268cec89179e8eaff8f44cfef6a0a979e4b4f28efbec24b32dc430891e3018aaf0671931f3a2c2eb9f4487d3443be5aabaab048180616375", + "blockHash": "0x80b5baa0856dbacc4e650e53775cc55fbd5d29063031f406e12779fdb60094dc", + "blockNumber": "0x235", + "blockTimestamp": "0x6a5dff8c", + "transactionHash": "0x497ada19b797d1c240c6c7cb561397a623496130664c463701aa24bcd9d053bc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x80b5baa0856dbacc4e650e53775cc55fbd5d29063031f406e12779fdb60094dc", + "blockNumber": "0x235", + "blockTimestamp": "0x6a5dff8c", + "transactionHash": "0x497ada19b797d1c240c6c7cb561397a623496130664c463701aa24bcd9d053bc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xe48d941b3f2821932af4fb4bc87dcfce0879a248d016f3de932bb9ca484db31ebc5b67600c3e2a1cbbdc7f7fda6883e6716e220f27f96ee6df7bd7868fa99e96", + "blockHash": "0x42f9689242e4818d1db30b0fcee83f177efd655a8fce5562268827bf9330972f", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dff8d", + "transactionHash": "0xc57d9444e3955f1a4e481e39754d5e8f3fc64cf955454c037eb1b88f135534b8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x42f9689242e4818d1db30b0fcee83f177efd655a8fce5562268827bf9330972f", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dff8d", + "transactionHash": "0xc57d9444e3955f1a4e481e39754d5e8f3fc64cf955454c037eb1b88f135534b8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xbc5b67600c3e2a1cbbdc7f7fda6883e6716e220f27f96ee6df7bd7868fa99e969e0f3af8d498a0f18b433368b0f2b4e3e6d3a8c41f1e5635e12e95910e895631", + "blockHash": "0x8170c649f88ac7429ff64b99c3a6707966f8e0e717fef13e24ea5fb2d731d132", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dff8e", + "transactionHash": "0xe979996b50e2c58e9e793c07a7a09b288b8f515901a4692bb63e13a4f04ecd73", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8170c649f88ac7429ff64b99c3a6707966f8e0e717fef13e24ea5fb2d731d132", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dff8e", + "transactionHash": "0xe979996b50e2c58e9e793c07a7a09b288b8f515901a4692bb63e13a4f04ecd73", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x2624f34de1cd3a862f9d1e846f5a0f2a850d8808d7be02f9afba36364bef76c8839a910db72070dfaecf39760eb3470c656bb32ffe8ed10f4b5ac751b0d0db39", + "blockHash": "0x8a98a261ead34b29b40e36155b43107f4b2ae7ff75977fb601e592cb4bf00c3f", + "blockNumber": "0x238", + "blockTimestamp": "0x6a5dff8f", + "transactionHash": "0xbf3b165f848f6702fc6c1de5f3c9914b0842c2378d383585b430186f096cc568", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8a98a261ead34b29b40e36155b43107f4b2ae7ff75977fb601e592cb4bf00c3f", + "blockNumber": "0x238", + "blockTimestamp": "0x6a5dff8f", + "transactionHash": "0xbf3b165f848f6702fc6c1de5f3c9914b0842c2378d383585b430186f096cc568", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x839a910db72070dfaecf39760eb3470c656bb32ffe8ed10f4b5ac751b0d0db39828a434cbae3a7dca6ca1c785e76863c6900914543445652f252c203618bbac6", + "blockHash": "0x62429caf811f9eb879431f56bc9f8f4ece1bc783825c0fe90395e3e355b839db", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dff8f", + "transactionHash": "0x4960447caa7dfa03c29a4bc90c7e8e9b3a47bbb8e8acc5da7155919f8d99e537", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x62429caf811f9eb879431f56bc9f8f4ece1bc783825c0fe90395e3e355b839db", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dff8f", + "transactionHash": "0x4960447caa7dfa03c29a4bc90c7e8e9b3a47bbb8e8acc5da7155919f8d99e537", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x9d965b4d9995d885ece790e82aac95863d3b7281ea4f582b31745c7751ec1c0f93ab62295ee9b4db758ea481e356642bd2bd220e83d9f5e1d1cc2bcf69fa2be0", + "blockHash": "0x4214018f17314da3d1a3bc3c5ed8c8797031d06519a900fab95b823427a9bed6", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dff90", + "transactionHash": "0xe4e0668db5f37b882d82bf68061f56edc21649807aaa911a54939803017b0e43", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4214018f17314da3d1a3bc3c5ed8c8797031d06519a900fab95b823427a9bed6", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dff90", + "transactionHash": "0xe4e0668db5f37b882d82bf68061f56edc21649807aaa911a54939803017b0e43", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x48842e338fbf01ca82777455c6f22a32b6d90c732395ad76a2622a8414eec8632aa62fd9c1dfe06050772f5eaef455f51b2f03679d1cf5f8af5104a4a0807948", + "blockHash": "0xaf6a7b40d5d5e85b319ef6e642559abad93e7a79678836c81fed3ddf2e2bb770", + "blockNumber": "0x23b", + "blockTimestamp": "0x6a5dff90", + "transactionHash": "0x61d3ac041600d20646de49e195866c34db5a06e3f007110a5761a6728eb77daa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xaf6a7b40d5d5e85b319ef6e642559abad93e7a79678836c81fed3ddf2e2bb770", + "blockNumber": "0x23b", + "blockTimestamp": "0x6a5dff90", + "transactionHash": "0x61d3ac041600d20646de49e195866c34db5a06e3f007110a5761a6728eb77daa", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xd8177521023ede030486e5e507c1460644ad0dd5409dc8b56ee84bfa43a4d8ee64d0e2880953d9b60c2f482f9d9d6a64aded02129ed049a0c25fa21420d96802", + "blockHash": "0x42c8a8b27c55299e7eb01fbe3aa9ca4b37a0e36689c31d4cdc67ea4bc0d5745f", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dff91", + "transactionHash": "0xb81f46db9393b4659c1e8fc8e92e5be6476eaaed34a5782d1c6081ba7afd92f6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x42c8a8b27c55299e7eb01fbe3aa9ca4b37a0e36689c31d4cdc67ea4bc0d5745f", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dff91", + "transactionHash": "0xb81f46db9393b4659c1e8fc8e92e5be6476eaaed34a5782d1c6081ba7afd92f6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x4868917e2b21891c1d20be33ee2f7f2dc6cdd0af206eca937370053ca1d80e8a87512fb070ca9952de59092821c74cff6d0e97fd3831ef887dcb7eacfd725c40", + "blockHash": "0x051afe505b6ca7bae77230e766cde32ef1210c69f6cbc662ab4b2a48f029d6d6", + "blockNumber": "0x23d", + "blockTimestamp": "0x6a5dff91", + "transactionHash": "0x87e303fc2325e0ce87cefd2233267481c77846d5757b600198b8775f93d51534", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x051afe505b6ca7bae77230e766cde32ef1210c69f6cbc662ab4b2a48f029d6d6", + "blockNumber": "0x23d", + "blockTimestamp": "0x6a5dff91", + "transactionHash": "0x87e303fc2325e0ce87cefd2233267481c77846d5757b600198b8775f93d51534", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xb52a724f651aab1447fdcdc2046e223b106533b7122250ec9796a6100da877de519e360d0e910a811fc62f434e9fc4c2b1c886e2975c66391fb7bdf296bf39ff", + "blockHash": "0x710b338ad4f4bd4376960e37617fef1bf7a11af021e32fed6c64464330380037", + "blockNumber": "0x23e", + "blockTimestamp": "0x6a5dff92", + "transactionHash": "0x5d791c89bf898da16b8978e2f63c87bae1abca61004c08bf1aa4bf2260ab91c0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x710b338ad4f4bd4376960e37617fef1bf7a11af021e32fed6c64464330380037", + "blockNumber": "0x23e", + "blockTimestamp": "0x6a5dff92", + "transactionHash": "0x5d791c89bf898da16b8978e2f63c87bae1abca61004c08bf1aa4bf2260ab91c0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x2ca34b48defa5999988b98d71765cda25b2680c3e55106b3e14a6a935450cc080fa4132773012737970cd2050fd06499cd2b0d9a8454f3394915f29617cf0bc7", + "blockHash": "0xac4d9e93f0d6e7577a884e4edc48d3090061d5d012a976c117e388c8bf39dbc9", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dff93", + "transactionHash": "0xd4b3e1c3bb5300968c7694a8ecdc8bd8189f96fb83ef0801be76d6b673104d2a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xac4d9e93f0d6e7577a884e4edc48d3090061d5d012a976c117e388c8bf39dbc9", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dff93", + "transactionHash": "0xd4b3e1c3bb5300968c7694a8ecdc8bd8189f96fb83ef0801be76d6b673104d2a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x60acd49664f3b1e243464b52630b739112f5e515ba80f714196273610274f642e182bcbbad187bacd23cd2fef39429e358e3c3808fd33e79a17d7ad1ef1b41e1", + "blockHash": "0x252aabd9b0ac954f341b5bcc859e004bfc735dbbb84c053dbb991597cd91920b", + "blockNumber": "0x240", + "blockTimestamp": "0x6a5dff94", + "transactionHash": "0x15096d7dfab3f8f79973cf8e737b8ccd67c060586c8555ea824cd4462f9f94f5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x252aabd9b0ac954f341b5bcc859e004bfc735dbbb84c053dbb991597cd91920b", + "blockNumber": "0x240", + "blockTimestamp": "0x6a5dff94", + "transactionHash": "0x15096d7dfab3f8f79973cf8e737b8ccd67c060586c8555ea824cd4462f9f94f5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x1a339efb35f796d0a92ade1dc34cdb09ae217be22a7a61a89b29037dd3f360f09e3d48048012e3ddc07eb10097e99ebeff4eec203737876295a1b895c79b417b", + "blockHash": "0xa4820ae580ade64574ffef352efbd462a42797579e0f90d6c117922212da3f6f", + "blockNumber": "0x241", + "blockTimestamp": "0x6a5dff94", + "transactionHash": "0xe51803db6386cf3cd707c93e6d9c05583724130d42577b453ebb1da95719f138", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa4820ae580ade64574ffef352efbd462a42797579e0f90d6c117922212da3f6f", + "blockNumber": "0x241", + "blockTimestamp": "0x6a5dff94", + "transactionHash": "0xe51803db6386cf3cd707c93e6d9c05583724130d42577b453ebb1da95719f138", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x7a57114f68825719fa108fd8dae142a67b95f3b60015933b12b3542b15a70e89ddb1a4ddca9186bac393487fceb0c59fdaac2a4caf97d2c46a95d331cfe57e6a", + "blockHash": "0xf4f3477a2b634ce18cf0c1ec4a78c3386cea6ac77eb39f0f58eeba2989f2f0ef", + "blockNumber": "0x242", + "blockTimestamp": "0x6a5dff95", + "transactionHash": "0x6a1f5a6e1eb028e75a4931aca8f10daf5fe6a5ae675ad8e690d5271a4799d365", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf4f3477a2b634ce18cf0c1ec4a78c3386cea6ac77eb39f0f58eeba2989f2f0ef", + "blockNumber": "0x242", + "blockTimestamp": "0x6a5dff95", + "transactionHash": "0x6a1f5a6e1eb028e75a4931aca8f10daf5fe6a5ae675ad8e690d5271a4799d365", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xecb0b2cc493712855fb4049ac38bdd737d6b19e5e6f9c3eae0496e97f3e38c4485678666f277ba62afc74662ccae59114a0110e379c410a532a3f8c05d6ed328", + "blockHash": "0x5194ed8473a0a50abe5cc6c507f573a30bb5df0845e3568ab33fa4353cbafaa5", + "blockNumber": "0x243", + "blockTimestamp": "0x6a5dff95", + "transactionHash": "0xfdcf04604214d1cc117b7c0fbc641bd80b56c912a969c284b5b1f3499a667d28", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5194ed8473a0a50abe5cc6c507f573a30bb5df0845e3568ab33fa4353cbafaa5", + "blockNumber": "0x243", + "blockTimestamp": "0x6a5dff95", + "transactionHash": "0xfdcf04604214d1cc117b7c0fbc641bd80b56c912a969c284b5b1f3499a667d28", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x36ae7140758ed90883021017bff2c56106ea58b99eefdc29f60276a6b2f8c2b5a8372b84d77ec27fae23d63d0e4517f06f5d5259ee35447c83988523c21fda6e", + "blockHash": "0xe5ac6fffd218d4c80077a66930ac55cf1873efea2859723415b2f9f84df4a6fc", + "blockNumber": "0x244", + "blockTimestamp": "0x6a5dff96", + "transactionHash": "0x5a51c53807d5f43009f45e1a1e16a5fe91ebe5b304acc0a171f9f396f2537c10", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe5ac6fffd218d4c80077a66930ac55cf1873efea2859723415b2f9f84df4a6fc", + "blockNumber": "0x244", + "blockTimestamp": "0x6a5dff96", + "transactionHash": "0x5a51c53807d5f43009f45e1a1e16a5fe91ebe5b304acc0a171f9f396f2537c10", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xaff11aa1fe7f78b9d18b9db3035fb479749417705cd38e7be7a10d70f86d314346d5afedd6ac461ace476863046ad32bc0ac335e92df5cddd64d2d3ac9f1f3d7", + "blockHash": "0x387fba734ded5d45f9d09bed734254ca9c2e8fdc8523b2ebcd0f7983ff9a58dd", + "blockNumber": "0x245", + "blockTimestamp": "0x6a5dff97", + "transactionHash": "0x67dbd2b70fbf97f1caf42520c29fa66062c703a3ecb1e0c5bde5f05597bbd8ea", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x387fba734ded5d45f9d09bed734254ca9c2e8fdc8523b2ebcd0f7983ff9a58dd", + "blockNumber": "0x245", + "blockTimestamp": "0x6a5dff97", + "transactionHash": "0x67dbd2b70fbf97f1caf42520c29fa66062c703a3ecb1e0c5bde5f05597bbd8ea", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x62eb95ca2c2e3945481bb2465f3712a89d905cfb554b53529fbc2f206cf62659fa5769e1177e05c9812def4dd31f4bc115f63b7be182dffb3e3bfcafd89119f7", + "blockHash": "0xa8550eb797358cfb6ae62f029a18609c164256e299f6590a9bed60b767c0850c", + "blockNumber": "0x246", + "blockTimestamp": "0x6a5dff98", + "transactionHash": "0xacaaae7a62c001620e6beb1604053a840af294b75217bb22b0464e44f8254a67", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa8550eb797358cfb6ae62f029a18609c164256e299f6590a9bed60b767c0850c", + "blockNumber": "0x246", + "blockTimestamp": "0x6a5dff98", + "transactionHash": "0xacaaae7a62c001620e6beb1604053a840af294b75217bb22b0464e44f8254a67", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x1db71c8c44f08c3641c032c16027b5c5af2785ac63030593277fb0feecdc34bbb6e87bf73b08051f73f6069f544448c1af4838b17faf2bb043457751e6792052", + "blockHash": "0xe1749db94bec23ae08bd033dd0cd41516df0b4c92531eaee19ccaa181792c376", + "blockNumber": "0x247", + "blockTimestamp": "0x6a5dff98", + "transactionHash": "0x81c6be0ac76890bac6335e7a55aaccbd094c970e51fe6edaa26cca2f617d263b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe1749db94bec23ae08bd033dd0cd41516df0b4c92531eaee19ccaa181792c376", + "blockNumber": "0x247", + "blockTimestamp": "0x6a5dff98", + "transactionHash": "0x81c6be0ac76890bac6335e7a55aaccbd094c970e51fe6edaa26cca2f617d263b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x5e7b4bb614850678c9fde2511384cb19e335f89b02ed20965e1558b25fc0c61e24893e2c2f326ed19f515cd48d26ba6f9cd1f17e42cf2f06102e5111ddb31e15", + "blockHash": "0xcd9abfc829935793d004b47ccd295666abba8523ea1a4d9828b26eeecfbde25f", + "blockNumber": "0x248", + "blockTimestamp": "0x6a5dff99", + "transactionHash": "0xb309b4ff7cd5a8e8d85b81ab5415e424496a55b48e94b1793cf5d052fd8b3dee", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcd9abfc829935793d004b47ccd295666abba8523ea1a4d9828b26eeecfbde25f", + "blockNumber": "0x248", + "blockTimestamp": "0x6a5dff99", + "transactionHash": "0xb309b4ff7cd5a8e8d85b81ab5415e424496a55b48e94b1793cf5d052fd8b3dee", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x351de84f97c8ccf5fc4077b293bd2189be4f43b8eaa0b6ff1d5789bd1189435c1799dbaad8d517d8090a3430e7d55c62215415ff94bee51b16fb55574f58ef13", + "blockHash": "0x809a8a4c1779578b796a1f14b629416f6c1bb1821c0bdca07f1fd273083fc407", + "blockNumber": "0x249", + "blockTimestamp": "0x6a5dff99", + "transactionHash": "0x8b83bcdaf9886c67d97f44e329224f900d027c85b210e7fb1dac9ba9f258b173", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x809a8a4c1779578b796a1f14b629416f6c1bb1821c0bdca07f1fd273083fc407", + "blockNumber": "0x249", + "blockTimestamp": "0x6a5dff99", + "transactionHash": "0x8b83bcdaf9886c67d97f44e329224f900d027c85b210e7fb1dac9ba9f258b173", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xa3a39e60ef26b4e6648d77eb26046772c8825ff3a0f946e1376b6ca4fda32c96893aa23ab3140bb58955bce1dff4e10d31797ccc231b1a7d5c02316f1531e1e0", + "blockHash": "0xd37a89dda5e92854f2a4bb4fe202b4110c09ff850b3002e49d6db7e9b23cdff6", + "blockNumber": "0x24a", + "blockTimestamp": "0x6a5dff9a", + "transactionHash": "0x532b0e2ca86491fd96bc32305f17e2bd9a2442fc6ecbaaea9d60750d57ce0373", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd37a89dda5e92854f2a4bb4fe202b4110c09ff850b3002e49d6db7e9b23cdff6", + "blockNumber": "0x24a", + "blockTimestamp": "0x6a5dff9a", + "transactionHash": "0x532b0e2ca86491fd96bc32305f17e2bd9a2442fc6ecbaaea9d60750d57ce0373", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0x878904471519d0353b95edaaa64c16289953d2b4e7620848e110527ea68e1b34d186edc317844e948a099af28c100aeb9ee9f2794bb2ea136418ea12294b6886", + "blockHash": "0x1b27354235492d5ac09daef9637bfe5c55aa13d31336fd7a963c3bb29745f0f8", + "blockNumber": "0x24b", + "blockTimestamp": "0x6a5dff9b", + "transactionHash": "0x18881d3301d001e709dba35a4949484d4e0547b6cb772ba2a6cf5e3f72decea4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1b27354235492d5ac09daef9637bfe5c55aa13d31336fd7a963c3bb29745f0f8", + "blockNumber": "0x24b", + "blockTimestamp": "0x6a5dff9b", + "transactionHash": "0x18881d3301d001e709dba35a4949484d4e0547b6cb772ba2a6cf5e3f72decea4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xecd1a00012153e0bda751e17d92fa3f3ac091e4e7395aabc6c0170ac3be99d1f356b4f59add06a396ccecaf6fb6489bc41731b49bb2f6868ce3740c57db1aee5", + "blockHash": "0x7b59b8065cbba571f4e68b8cba3a5ed8891986b5f725f10733b0d27cf0d17ac5", + "blockNumber": "0x24c", + "blockTimestamp": "0x6a5dff9b", + "transactionHash": "0x1545dfffc186a0b4371fa52c458876f295417f1357782a5382a0eac92fd24d7f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7b59b8065cbba571f4e68b8cba3a5ed8891986b5f725f10733b0d27cf0d17ac5", + "blockNumber": "0x24c", + "blockTimestamp": "0x6a5dff9b", + "transactionHash": "0x1545dfffc186a0b4371fa52c458876f295417f1357782a5382a0eac92fd24d7f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab" + ], + "data": "0xd3e614f3269e491a00e63f1edd498f9295af07bf100011f23947fe42cd929fa06f631eb4679195a4fea3f9c6fa883c36aa4392539d1e3a05465351dd7ff784e2", + "blockHash": "0x7695da21b8498f5d76a4ca848d10d5fae41a9c984feec9b8c1f485931d4cd005", + "blockNumber": "0x24d", + "blockTimestamp": "0x6a5dff9c", + "transactionHash": "0xf8bd907d2344dcd17584c0bf7b63b02e5862e5bea8be87bb40cc95500ae85c2d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7695da21b8498f5d76a4ca848d10d5fae41a9c984feec9b8c1f485931d4cd005", + "blockNumber": "0x24d", + "blockTimestamp": "0x6a5dff9c", + "transactionHash": "0xf8bd907d2344dcd17584c0bf7b63b02e5862e5bea8be87bb40cc95500ae85c2d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x61861021df9982c019354b6be2bbbe16ad1f9a7ba6ad2ff247f1f77fbf8bca00", + "blockNumber": "0x24e", + "blockTimestamp": "0x6a5dff9d", + "transactionHash": "0xef29fdbe08f96e513f088f0cc199113643c3f5dce193a490ee36afcb4d457534", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xb9ad2df6539f71edccb2e21697bc404dcc2387aa097f704a1be2d9347fac58ab", + "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f", + "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea98" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xb418993bf6b0645856070c19184de128479c160268d3d72b46b55905521a2821", + "blockNumber": "0x24f", + "blockTimestamp": "0x6a5dff9e", + "transactionHash": "0xdc475ed151b2a51f6150631596876c125884ef49c5c4437b841abcf72707eeb0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003851d000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb418993bf6b0645856070c19184de128479c160268d3d72b46b55905521a2821", + "blockNumber": "0x24f", + "blockTimestamp": "0x6a5dff9e", + "transactionHash": "0xdc475ed151b2a51f6150631596876c125884ef49c5c4437b841abcf72707eeb0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xf7974db1dfb2f3ba7701d684406818e39d0529e7d7abca6860130226fa30dabc", + "0x4b5139c9af393fc5321c8a6a38e6172b2945cd5512c76146642ceb768e9f0a7a", + "0x371a90287dfd9fd222fcdece05e767664e807f62a5b5a038f14f704557d6500c" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x2ea46042dec12308d5f75a4c1e13eb57230ba98e8b78ed5fe7fd04d23a67e2f2", + "blockNumber": "0x2dc", + "blockTimestamp": "0x6a5dffda", + "transactionHash": "0xbe82e5565328e7f8223a15c034449676cf065117d5f42518b690032d9eeffc77", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000011555000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2ea46042dec12308d5f75a4c1e13eb57230ba98e8b78ed5fe7fd04d23a67e2f2", + "blockNumber": "0x2dc", + "blockTimestamp": "0x6a5dffda", + "transactionHash": "0xbe82e5565328e7f8223a15c034449676cf065117d5f42518b690032d9eeffc77", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xfce58a380dc640694b7919683ed34652f0fdadd05bd13b1c7fa2d565696a5840", + "0xde7c61c9c3bf0570e7b0da84a595c7093cebf9dafdf43cfad59f694971971182", + "0xf323408e1bab99166ada150f17c4ece1fc95a019541f43cc019b0e3b55e70117" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0x1e76e6ba77ed748e93c5ac806e5e8c7461a55b1e0e1b51104d2913b3565d8afe", + "blockNumber": "0x359", + "blockTimestamp": "0x6a5e000f", + "transactionHash": "0x4099e80055afa5a80d0c03c20f78cd81aaa6327caa17a89c4fa5fa1d0275449f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001c055000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1e76e6ba77ed748e93c5ac806e5e8c7461a55b1e0e1b51104d2913b3565d8afe", + "blockNumber": "0x359", + "blockTimestamp": "0x6a5e000f", + "transactionHash": "0x4099e80055afa5a80d0c03c20f78cd81aaa6327caa17a89c4fa5fa1d0275449f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d", + "0x4b5139c9af393fc5321c8a6a38e6172b2945cd5512c76146642ceb768e9f0a7a", + "0x361754da7a2b2f85b7c35b83aa161972b2eac1c1a3ae1eb0d31cfa9961b831e0" + ], + "data": "0x1f4c416d2e97d4e3c56bd833ac0cb5fab9a0a99487a7af801a64d5c728df8d7e", + "blockHash": "0xa929429fee6a1470e73cee7127525c106cb6589f3a17692788872c258cb20730", + "blockNumber": "0x35e", + "blockTimestamp": "0x6a5e0010", + "transactionHash": "0x25ddfab34e7cea039f4c746dd59f502bc604011c18bb324f5ef4dd2caf244b30", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x1349309f3dd560eb1af02b0ce29d2a52fa899e5b67f944dbc4641bd111114561", + "0xa7889bf75b4a3a0e5bde45eb99cbd39cd26dfc83570b3772872b37afa15baf2a", + "0x361754da7a2b2f85b7c35b83aa161972b2eac1c1a3ae1eb0d31cfa9961b831e0" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0xa929429fee6a1470e73cee7127525c106cb6589f3a17692788872c258cb20730", + "blockNumber": "0x35e", + "blockTimestamp": "0x6a5e0010", + "transactionHash": "0x25ddfab34e7cea039f4c746dd59f502bc604011c18bb324f5ef4dd2caf244b30", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000022fcc000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa929429fee6a1470e73cee7127525c106cb6589f3a17692788872c258cb20730", + "blockNumber": "0x35e", + "blockTimestamp": "0x6a5e0010", + "transactionHash": "0x25ddfab34e7cea039f4c746dd59f502bc604011c18bb324f5ef4dd2caf244b30", + "transactionIndex": "0x0", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x1f4c416d2e97d4e3c56bd833ac0cb5fab9a0a99487a7af801a64d5c728df8d7efe430d03478290c96836c6ff22e39e484ed84d9fffea68624da4e9046cc6799d", + "blockHash": "0xd668f815dbba183a1dcc4313ef5e7b6513e8c7a2f30c433b03b3bb0dd81ba11d", + "blockNumber": "0x363", + "blockTimestamp": "0x6a5e0012", + "transactionHash": "0xaba2ba89713a6349ba3f153b69a345242a68297b21718926e5fc0b9a8c3b61ab", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd668f815dbba183a1dcc4313ef5e7b6513e8c7a2f30c433b03b3bb0dd81ba11d", + "blockNumber": "0x363", + "blockTimestamp": "0x6a5e0012", + "transactionHash": "0xaba2ba89713a6349ba3f153b69a345242a68297b21718926e5fc0b9a8c3b61ab", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xfe430d03478290c96836c6ff22e39e484ed84d9fffea68624da4e9046cc6799d03b7cffd542289fdc07d0deec557224675b2f3047adf9d70f236eaacfd384350", + "blockHash": "0x57599d34f35ce37eab99a17beffebb498e8c99fb834500cdf2e7331dd22e6231", + "blockNumber": "0x364", + "blockTimestamp": "0x6a5e0012", + "transactionHash": "0x1c0a6f0444e395f7dd10f22565814e9da0fd4bbe4cc3081e81cc732378a0cee4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x57599d34f35ce37eab99a17beffebb498e8c99fb834500cdf2e7331dd22e6231", + "blockNumber": "0x364", + "blockTimestamp": "0x6a5e0012", + "transactionHash": "0x1c0a6f0444e395f7dd10f22565814e9da0fd4bbe4cc3081e81cc732378a0cee4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x03b7cffd542289fdc07d0deec557224675b2f3047adf9d70f236eaacfd38435036a5391f22cd573e281d8e97c1da03083d68c146d9c14a33b928401c4c0ee4da", + "blockHash": "0x6883127150b20b93ffa94a4d6de08b73212ac139cbbe89d687da33c3cd2f8a6d", + "blockNumber": "0x369", + "blockTimestamp": "0x6a5e0013", + "transactionHash": "0x6457a86a325cf8b47f006482d518f86b5a2a0fd3478d6cb65f55a10d8d68070f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6883127150b20b93ffa94a4d6de08b73212ac139cbbe89d687da33c3cd2f8a6d", + "blockNumber": "0x369", + "blockTimestamp": "0x6a5e0013", + "transactionHash": "0x6457a86a325cf8b47f006482d518f86b5a2a0fd3478d6cb65f55a10d8d68070f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x36a5391f22cd573e281d8e97c1da03083d68c146d9c14a33b928401c4c0ee4da87846904346675c14b3bc95f8177d24d478fad8035743c6a49026352b3833052", + "blockHash": "0x22e3b4db6b791fd00253ca82e58481667e0c176d299fe5555418aeee8a0307ab", + "blockNumber": "0x36a", + "blockTimestamp": "0x6a5e0014", + "transactionHash": "0x03ffcd370f1ddafd8503002dd219f7fbcf5a48a81adbe5d30ba8f89403dc792d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x22e3b4db6b791fd00253ca82e58481667e0c176d299fe5555418aeee8a0307ab", + "blockNumber": "0x36a", + "blockTimestamp": "0x6a5e0014", + "transactionHash": "0x03ffcd370f1ddafd8503002dd219f7fbcf5a48a81adbe5d30ba8f89403dc792d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x87846904346675c14b3bc95f8177d24d478fad8035743c6a49026352b3833052b22538e1ef0893948936405d81066884c59102785f413d3c9b0003fbc467d9ba", + "blockHash": "0x55f58e9222a64f7f01bcfe627dfbb1887879bbac18a438efa358a2a9a362ad17", + "blockNumber": "0x36f", + "blockTimestamp": "0x6a5e0015", + "transactionHash": "0x4663ad6a7372428220505543c9f65aa7c10ca71282fdce2c1a3de9f482870098", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x55f58e9222a64f7f01bcfe627dfbb1887879bbac18a438efa358a2a9a362ad17", + "blockNumber": "0x36f", + "blockTimestamp": "0x6a5e0015", + "transactionHash": "0x4663ad6a7372428220505543c9f65aa7c10ca71282fdce2c1a3de9f482870098", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xb22538e1ef0893948936405d81066884c59102785f413d3c9b0003fbc467d9ba4d7f543e828f8d886c15d82d1d96964a9b1d4c6bccba5aaa72c694055bbf151f", + "blockHash": "0x07c1b9bd43d779deac7dd09aa7774303c504e52aafc09a8e726ed65c8571b7b1", + "blockNumber": "0x370", + "blockTimestamp": "0x6a5e0015", + "transactionHash": "0x6068f213b63b89bd02d4b7878a39a521e5e25fb83b8e563113c55260d01583bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x07c1b9bd43d779deac7dd09aa7774303c504e52aafc09a8e726ed65c8571b7b1", + "blockNumber": "0x370", + "blockTimestamp": "0x6a5e0015", + "transactionHash": "0x6068f213b63b89bd02d4b7878a39a521e5e25fb83b8e563113c55260d01583bb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x4d7f543e828f8d886c15d82d1d96964a9b1d4c6bccba5aaa72c694055bbf151f00713cb3d85751ffee8f8e0f336f51a361d3f8c28b6e2830349d8ae48d06b720", + "blockHash": "0x308df883092d0a822a54286453823f93e40f1607604bcf2af965972dec3cdb12", + "blockNumber": "0x375", + "blockTimestamp": "0x6a5e0017", + "transactionHash": "0xe7820836fa7bc2dee978e8e98d86296a0a5cb3db8ef1ce818e84e86539a22715", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x308df883092d0a822a54286453823f93e40f1607604bcf2af965972dec3cdb12", + "blockNumber": "0x375", + "blockTimestamp": "0x6a5e0017", + "transactionHash": "0xe7820836fa7bc2dee978e8e98d86296a0a5cb3db8ef1ce818e84e86539a22715", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x00713cb3d85751ffee8f8e0f336f51a361d3f8c28b6e2830349d8ae48d06b720e5f27ac4abbdce2644e120d9e5b1dcc0512ca6cd16ec67acc3b415a92f21f435", + "blockHash": "0xabf23d7c0c9765f69ce44b873c9ad58efa954fab5c5e2c91871f781181a1abe1", + "blockNumber": "0x376", + "blockTimestamp": "0x6a5e0017", + "transactionHash": "0xdfbb3e0eda19527ea7ff5c14c50242d010dd4b5eccff1261a6d5daf99a2a1402", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xabf23d7c0c9765f69ce44b873c9ad58efa954fab5c5e2c91871f781181a1abe1", + "blockNumber": "0x376", + "blockTimestamp": "0x6a5e0017", + "transactionHash": "0xdfbb3e0eda19527ea7ff5c14c50242d010dd4b5eccff1261a6d5daf99a2a1402", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xe5f27ac4abbdce2644e120d9e5b1dcc0512ca6cd16ec67acc3b415a92f21f4352d811f615057012881f8dfbf729f4781e96f788c1199a72d8d892f79dbfbd5fc", + "blockHash": "0x2048d45a869a7e01ef01cbce5458403418568d40338e5958882decf76a04feb4", + "blockNumber": "0x37b", + "blockTimestamp": "0x6a5e0018", + "transactionHash": "0xebc14d203921308f9199b70982fffeab4b7ddebce149601e605988fa27ff9ced", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2048d45a869a7e01ef01cbce5458403418568d40338e5958882decf76a04feb4", + "blockNumber": "0x37b", + "blockTimestamp": "0x6a5e0018", + "transactionHash": "0xebc14d203921308f9199b70982fffeab4b7ddebce149601e605988fa27ff9ced", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x2d811f615057012881f8dfbf729f4781e96f788c1199a72d8d892f79dbfbd5fc7e1cf71dd893b21c171aba4a5edc98064cbfb479502f1de4dcf468b9777561f7", + "blockHash": "0xc56b0713edaf8d587dcec4f0254dc5b8ec3471c5588c06fe9de28f120ad31f76", + "blockNumber": "0x37c", + "blockTimestamp": "0x6a5e0019", + "transactionHash": "0x9b50647de044009f5b3009ecd3902b228d6acbbd304f4d0bb67c397a3eed4a55", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc56b0713edaf8d587dcec4f0254dc5b8ec3471c5588c06fe9de28f120ad31f76", + "blockNumber": "0x37c", + "blockTimestamp": "0x6a5e0019", + "transactionHash": "0x9b50647de044009f5b3009ecd3902b228d6acbbd304f4d0bb67c397a3eed4a55", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x7e1cf71dd893b21c171aba4a5edc98064cbfb479502f1de4dcf468b9777561f73cc83253d48e582250a53377f5a26f7cb2c24ef5e4e2e287a66bbe8d0ab9c377", + "blockHash": "0x2d3a0d1d4788b8c159db485032b45438b45782384ed6ec1324fa0b2e2f882068", + "blockNumber": "0x381", + "blockTimestamp": "0x6a5e001a", + "transactionHash": "0x064b1d965a340a87ce137ee6ad2e17f780c7949f2e7bac5154053a6186e72c4f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2d3a0d1d4788b8c159db485032b45438b45782384ed6ec1324fa0b2e2f882068", + "blockNumber": "0x381", + "blockTimestamp": "0x6a5e001a", + "transactionHash": "0x064b1d965a340a87ce137ee6ad2e17f780c7949f2e7bac5154053a6186e72c4f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x3cc83253d48e582250a53377f5a26f7cb2c24ef5e4e2e287a66bbe8d0ab9c377729da17ae94af4d11da62dd683636a0677f8e1940f0299ba3f9a8400e48a35e7", + "blockHash": "0x70eff46e47d1522f393ec20e074f132074c1573c018113e007871997587d1821", + "blockNumber": "0x382", + "blockTimestamp": "0x6a5e001a", + "transactionHash": "0xa0b6ff35fb59a669fe1242de1d3d8ac85c3fa02d1223fefffe0a395b925382c7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x70eff46e47d1522f393ec20e074f132074c1573c018113e007871997587d1821", + "blockNumber": "0x382", + "blockTimestamp": "0x6a5e001a", + "transactionHash": "0xa0b6ff35fb59a669fe1242de1d3d8ac85c3fa02d1223fefffe0a395b925382c7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x729da17ae94af4d11da62dd683636a0677f8e1940f0299ba3f9a8400e48a35e7d9c8c639b5f73abde95c2773c682aeabb45b41115adf0c11d0f33d8e3a7e955a", + "blockHash": "0xbc1faf6bde9e5a1a93e6f651f125422e2fa755f92d649d2a260d664e6c5ace73", + "blockNumber": "0x387", + "blockTimestamp": "0x6a5e001c", + "transactionHash": "0x6596b4ed6bb938599c746e60765931e4247271bd794a465683d4be11f7f5e0c2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbc1faf6bde9e5a1a93e6f651f125422e2fa755f92d649d2a260d664e6c5ace73", + "blockNumber": "0x387", + "blockTimestamp": "0x6a5e001c", + "transactionHash": "0x6596b4ed6bb938599c746e60765931e4247271bd794a465683d4be11f7f5e0c2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xd9c8c639b5f73abde95c2773c682aeabb45b41115adf0c11d0f33d8e3a7e955a1802f750a8982eafb620275a8768ec274abfa565937b7860572bd143206fe6a3", + "blockHash": "0xea9c05a17c258e209eaa3f1b683864681d91109392bc995550b59cca268e8530", + "blockNumber": "0x388", + "blockTimestamp": "0x6a5e001c", + "transactionHash": "0xfacaca920ca3454cb457e78a4eeade70061fb4f603e54067988b9770ab7dcbf5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xea9c05a17c258e209eaa3f1b683864681d91109392bc995550b59cca268e8530", + "blockNumber": "0x388", + "blockTimestamp": "0x6a5e001c", + "transactionHash": "0xfacaca920ca3454cb457e78a4eeade70061fb4f603e54067988b9770ab7dcbf5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x1802f750a8982eafb620275a8768ec274abfa565937b7860572bd143206fe6a3057e20985297deb5753112ddefe4182e3128b86357ba2ce872777add72e05e4e", + "blockHash": "0xc7af2a6de6bc3a80dedf09b8ad0a488753624fc305d0979193187b05e7ca0d14", + "blockNumber": "0x38d", + "blockTimestamp": "0x6a5e001d", + "transactionHash": "0x88ea67f1201e21e79e5cd5d73c5575bb8e5e05b3eb07f5f411aa589d0c91e8f6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc7af2a6de6bc3a80dedf09b8ad0a488753624fc305d0979193187b05e7ca0d14", + "blockNumber": "0x38d", + "blockTimestamp": "0x6a5e001d", + "transactionHash": "0x88ea67f1201e21e79e5cd5d73c5575bb8e5e05b3eb07f5f411aa589d0c91e8f6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x057e20985297deb5753112ddefe4182e3128b86357ba2ce872777add72e05e4edf1690b5d49c2ddc5ec7e8e9592be7d0b630cdf1af5309f3015ca776de8aba1f", + "blockHash": "0x641a9fcbf23064ca5fb201799fdec7b3aa5cd0bfe0f6bbb9d019572d21eea28a", + "blockNumber": "0x38e", + "blockTimestamp": "0x6a5e001d", + "transactionHash": "0x3e612fac1c1e7eae2041755ae25495ae760952e60e9b465ae0963cff841d24ac", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x641a9fcbf23064ca5fb201799fdec7b3aa5cd0bfe0f6bbb9d019572d21eea28a", + "blockNumber": "0x38e", + "blockTimestamp": "0x6a5e001d", + "transactionHash": "0x3e612fac1c1e7eae2041755ae25495ae760952e60e9b465ae0963cff841d24ac", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xdf1690b5d49c2ddc5ec7e8e9592be7d0b630cdf1af5309f3015ca776de8aba1f25d723a2ac25eb280a41c1ecf72ad93899cb6901229f077063b612391939c03a", + "blockHash": "0x09185d8dd0f5f436f2c9f6a6bf416ebffb1b4e7bcb8b2160556049c919bf5e92", + "blockNumber": "0x38f", + "blockTimestamp": "0x6a5e001e", + "transactionHash": "0x9367e4859feeba0c4cfac08286c9a6e3464f05951239a9f61be9ea02d0b73ba8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x09185d8dd0f5f436f2c9f6a6bf416ebffb1b4e7bcb8b2160556049c919bf5e92", + "blockNumber": "0x38f", + "blockTimestamp": "0x6a5e001e", + "transactionHash": "0x9367e4859feeba0c4cfac08286c9a6e3464f05951239a9f61be9ea02d0b73ba8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x25d723a2ac25eb280a41c1ecf72ad93899cb6901229f077063b612391939c03a22d0eec1b8178bc2f5c50d1bf3ef712a5f53e82869c4fe965317f3d223da622b", + "blockHash": "0x03781c637871e059b86814644c338a2da1523cac5924c4e963631a145ac734d7", + "blockNumber": "0x390", + "blockTimestamp": "0x6a5e001e", + "transactionHash": "0xc527112520e1a4c49e309c5be657064fb41066237babd086b65d0a200d3c1b13", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x03781c637871e059b86814644c338a2da1523cac5924c4e963631a145ac734d7", + "blockNumber": "0x390", + "blockTimestamp": "0x6a5e001e", + "transactionHash": "0xc527112520e1a4c49e309c5be657064fb41066237babd086b65d0a200d3c1b13", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x22d0eec1b8178bc2f5c50d1bf3ef712a5f53e82869c4fe965317f3d223da622b0dca4c3a04ef6a1a2d86397e420d0b1b3667b00020ea154726a82f49e2b82d50", + "blockHash": "0xc38c5e6607f0f260b607cc60614d6e564011827eaf4ecc1d5b9d7fd80bc5f3af", + "blockNumber": "0x395", + "blockTimestamp": "0x6a5e001f", + "transactionHash": "0x2739a13c2972828dbabd922287d1ff8361cb6edc1b9856ef2aa99d1a7bf035e8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc38c5e6607f0f260b607cc60614d6e564011827eaf4ecc1d5b9d7fd80bc5f3af", + "blockNumber": "0x395", + "blockTimestamp": "0x6a5e001f", + "transactionHash": "0x2739a13c2972828dbabd922287d1ff8361cb6edc1b9856ef2aa99d1a7bf035e8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x0dca4c3a04ef6a1a2d86397e420d0b1b3667b00020ea154726a82f49e2b82d5038b8642360da4b9ade3f3899f51c5c43c70f31c46b373830639915591d48cbba", + "blockHash": "0x8e7515d31dd255e7dc52239249d845332bce368561081993ead5c4ea99cc1bf6", + "blockNumber": "0x396", + "blockTimestamp": "0x6a5e001f", + "transactionHash": "0xe414fe68a5c9a42bb28ca0594b8a2e837431e392428d0c5d9f6912d4633ba776", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8e7515d31dd255e7dc52239249d845332bce368561081993ead5c4ea99cc1bf6", + "blockNumber": "0x396", + "blockTimestamp": "0x6a5e001f", + "transactionHash": "0xe414fe68a5c9a42bb28ca0594b8a2e837431e392428d0c5d9f6912d4633ba776", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x38b8642360da4b9ade3f3899f51c5c43c70f31c46b373830639915591d48cbba374d2a7f6f9b310a1c99b1d4defc1c3aa1de0060ccda3c1ae42edb412f714ac6", + "blockHash": "0x94d7693a6b09d3982d9afa8cc3c9741b07febc2d5ea52256f16122ef962f04e3", + "blockNumber": "0x39b", + "blockTimestamp": "0x6a5e0021", + "transactionHash": "0x01333bab74833ac02c67e715036f8142fc2b98d63f11006a14f44db17f6cc745", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x94d7693a6b09d3982d9afa8cc3c9741b07febc2d5ea52256f16122ef962f04e3", + "blockNumber": "0x39b", + "blockTimestamp": "0x6a5e0021", + "transactionHash": "0x01333bab74833ac02c67e715036f8142fc2b98d63f11006a14f44db17f6cc745", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x374d2a7f6f9b310a1c99b1d4defc1c3aa1de0060ccda3c1ae42edb412f714ac605fbfce9f2a3cf1dee8888f0efd3d92214e1247d24ffb83dae6113608bde454d", + "blockHash": "0x7789874de8eac9f08f4f71f58b5c894793df5f422d894d79b3ed6afbcf2c9b30", + "blockNumber": "0x39c", + "blockTimestamp": "0x6a5e0021", + "transactionHash": "0x0514300d5d1502f606e4f92a2b3329239ca7b2dec00aa525a1b01b61fa2038fb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7789874de8eac9f08f4f71f58b5c894793df5f422d894d79b3ed6afbcf2c9b30", + "blockNumber": "0x39c", + "blockTimestamp": "0x6a5e0021", + "transactionHash": "0x0514300d5d1502f606e4f92a2b3329239ca7b2dec00aa525a1b01b61fa2038fb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x05fbfce9f2a3cf1dee8888f0efd3d92214e1247d24ffb83dae6113608bde454d046fa21dfff5a80a76b1a72121a2896097238e8f2c472ffddb66d6069d25748e", + "blockHash": "0x63c6a28ed1f89ef21886d827c1cf7a259b799858a2c383f259f09e1ad00d6e26", + "blockNumber": "0x3a1", + "blockTimestamp": "0x6a5e0023", + "transactionHash": "0x8b2aeef94c7aa0895c45b66961c7e6b7a276a731bc9fe628f1759c75bf63b3d2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x63c6a28ed1f89ef21886d827c1cf7a259b799858a2c383f259f09e1ad00d6e26", + "blockNumber": "0x3a1", + "blockTimestamp": "0x6a5e0023", + "transactionHash": "0x8b2aeef94c7aa0895c45b66961c7e6b7a276a731bc9fe628f1759c75bf63b3d2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x046fa21dfff5a80a76b1a72121a2896097238e8f2c472ffddb66d6069d25748e5cb4c28650e1e4af902c38fe212aa330280d4d7bbd0f5c8033c8f45544585abd", + "blockHash": "0x822fe80b679db2496e65a0837965f835adadf406bc413fc3ef37ecc17cfbf2a3", + "blockNumber": "0x3a2", + "blockTimestamp": "0x6a5e0023", + "transactionHash": "0xa052a3d1f933395545525fbbc7df56d2a9b61dfb6fb94c71f6861d188b5cb302", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x822fe80b679db2496e65a0837965f835adadf406bc413fc3ef37ecc17cfbf2a3", + "blockNumber": "0x3a2", + "blockTimestamp": "0x6a5e0023", + "transactionHash": "0xa052a3d1f933395545525fbbc7df56d2a9b61dfb6fb94c71f6861d188b5cb302", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x5cb4c28650e1e4af902c38fe212aa330280d4d7bbd0f5c8033c8f45544585abdce680b50771e066ff2d7ae30fdf9c3c24373ab66b9c428285bc5862175f7184a", + "blockHash": "0x39aa72b2513331ad5a8c61bfccf9ca435e9c538f19d245c6c9fa086fc00b630e", + "blockNumber": "0x3a7", + "blockTimestamp": "0x6a5e0024", + "transactionHash": "0xa84f998e38cb20fd9583a3b969f106e1246380c613125e8b4baf3d0748c37415", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x39aa72b2513331ad5a8c61bfccf9ca435e9c538f19d245c6c9fa086fc00b630e", + "blockNumber": "0x3a7", + "blockTimestamp": "0x6a5e0024", + "transactionHash": "0xa84f998e38cb20fd9583a3b969f106e1246380c613125e8b4baf3d0748c37415", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xce680b50771e066ff2d7ae30fdf9c3c24373ab66b9c428285bc5862175f7184a8ca82fd0719d56c0ae713cd0f74a48bffecc469df08a87f61181e5deac83d352", + "blockHash": "0xe5c35f1d9ba3929bfc6fb983f722c6ce450abb12f6cfe9c41e4f92fdb013c20c", + "blockNumber": "0x3a8", + "blockTimestamp": "0x6a5e0024", + "transactionHash": "0x302f75a01e7df7fd43e114486284648c374576cb078158a97867ba06c59b3e39", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe5c35f1d9ba3929bfc6fb983f722c6ce450abb12f6cfe9c41e4f92fdb013c20c", + "blockNumber": "0x3a8", + "blockTimestamp": "0x6a5e0024", + "transactionHash": "0x302f75a01e7df7fd43e114486284648c374576cb078158a97867ba06c59b3e39", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x8ca82fd0719d56c0ae713cd0f74a48bffecc469df08a87f61181e5deac83d352deb510fe095a8dc9396bc5eebde157ffed6b7fae8a34fd80327e6c36898de28d", + "blockHash": "0xdbdb9328aac214066df638edd07dbbafca64f3ab1d764d21eb711e2ff4dd6802", + "blockNumber": "0x3ad", + "blockTimestamp": "0x6a5e0026", + "transactionHash": "0xac9b5b5d714d44aa53a276fae0aad857fd79ca6ac299b34e27dafed7d073bcdf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdbdb9328aac214066df638edd07dbbafca64f3ab1d764d21eb711e2ff4dd6802", + "blockNumber": "0x3ad", + "blockTimestamp": "0x6a5e0026", + "transactionHash": "0xac9b5b5d714d44aa53a276fae0aad857fd79ca6ac299b34e27dafed7d073bcdf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xdeb510fe095a8dc9396bc5eebde157ffed6b7fae8a34fd80327e6c36898de28d93ab97ca5316212af47671b44b373cde099d29aa056fe2b15b850bf8fb41e0ec", + "blockHash": "0x23472b22b230ef7b2de132dc34ef4604f43e4d9e8d80d804a9c868f24af1a876", + "blockNumber": "0x3ae", + "blockTimestamp": "0x6a5e0027", + "transactionHash": "0x02a0ff73074a84e1a97780be46ac15102ca17d72c3409f57c9f27f54906d88cb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x23472b22b230ef7b2de132dc34ef4604f43e4d9e8d80d804a9c868f24af1a876", + "blockNumber": "0x3ae", + "blockTimestamp": "0x6a5e0027", + "transactionHash": "0x02a0ff73074a84e1a97780be46ac15102ca17d72c3409f57c9f27f54906d88cb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x93ab97ca5316212af47671b44b373cde099d29aa056fe2b15b850bf8fb41e0ec36a3855a0c8ae5efa821f76c0139690c9b22c407751ad8ff049f7ea510d4bcfb", + "blockHash": "0x4a60c27ef2fa735c6690273798188d653cdde34055ddbb185d57bbb825729d41", + "blockNumber": "0x3b3", + "blockTimestamp": "0x6a5e0028", + "transactionHash": "0x70b085e23040d58e5bd5cd9e0deccc504eb946d10122e279fca83f76d7014fce", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4a60c27ef2fa735c6690273798188d653cdde34055ddbb185d57bbb825729d41", + "blockNumber": "0x3b3", + "blockTimestamp": "0x6a5e0028", + "transactionHash": "0x70b085e23040d58e5bd5cd9e0deccc504eb946d10122e279fca83f76d7014fce", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x36a3855a0c8ae5efa821f76c0139690c9b22c407751ad8ff049f7ea510d4bcfb4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc", + "blockHash": "0x06c702db88537a262eaaca21575dde496238f69981dcbc8632464f3e728767c1", + "blockNumber": "0x3b4", + "blockTimestamp": "0x6a5e0029", + "transactionHash": "0xc74973d7149a0f0eac3cdf97d2cce4a23454637b054b2728c77004faa325a1c8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x06c702db88537a262eaaca21575dde496238f69981dcbc8632464f3e728767c1", + "blockNumber": "0x3b4", + "blockTimestamp": "0x6a5e0029", + "transactionHash": "0xc74973d7149a0f0eac3cdf97d2cce4a23454637b054b2728c77004faa325a1c8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc4b9042f7e6d7351e0894ddc6ca5daa93d443a32c7c73362c1680aed961bc071f", + "blockHash": "0x45b50eccf9b4faff6644d65b2ef5120356d1ebeb2ca5a705170496182c121f8e", + "blockNumber": "0x3b9", + "blockTimestamp": "0x6a5e0029", + "transactionHash": "0xe2c902aaaad32e25624b23600103ce7c5a7043e7596d778ac951e1e0f45def70", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x45b50eccf9b4faff6644d65b2ef5120356d1ebeb2ca5a705170496182c121f8e", + "blockNumber": "0x3b9", + "blockTimestamp": "0x6a5e0029", + "transactionHash": "0xe2c902aaaad32e25624b23600103ce7c5a7043e7596d778ac951e1e0f45def70", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x4b9042f7e6d7351e0894ddc6ca5daa93d443a32c7c73362c1680aed961bc071f693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5", + "blockHash": "0x92c0642f441edbb9bf9bcefef3052ca75e2c479fb7b88aa98c0d89e119e199be", + "blockNumber": "0x3ba", + "blockTimestamp": "0x6a5e002a", + "transactionHash": "0xc1196c6161994bc1dd3a9e2b219865549db22b1702d53ab1938c31fc9bcd7053", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x92c0642f441edbb9bf9bcefef3052ca75e2c479fb7b88aa98c0d89e119e199be", + "blockNumber": "0x3ba", + "blockTimestamp": "0x6a5e002a", + "transactionHash": "0xc1196c6161994bc1dd3a9e2b219865549db22b1702d53ab1938c31fc9bcd7053", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5eb981b686150367f21e2e78fdbc74977afb887cdc5749c5f1e09f615691a1f0e", + "blockHash": "0x6a5e045bf5c9e22e0be59396cd0d9896be6863d3f9a78d1dae09847c1fad787d", + "blockNumber": "0x3bf", + "blockTimestamp": "0x6a5e002b", + "transactionHash": "0xfdcac91b526c2fca6371f6aee5287b9242ee4f53a959ffae590a83dc5aa1debe", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6a5e045bf5c9e22e0be59396cd0d9896be6863d3f9a78d1dae09847c1fad787d", + "blockNumber": "0x3bf", + "blockTimestamp": "0x6a5e002b", + "transactionHash": "0xfdcac91b526c2fca6371f6aee5287b9242ee4f53a959ffae590a83dc5aa1debe", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xeb981b686150367f21e2e78fdbc74977afb887cdc5749c5f1e09f615691a1f0e2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bdd", + "blockHash": "0x812591fc0c9434c2e7ac4ab9654ace26808907c7e6a4481568a2a5fb47f0ca29", + "blockNumber": "0x3c0", + "blockTimestamp": "0x6a5e002c", + "transactionHash": "0x3e3e995f9bf0770cf237b6c9b669565b3a48d91b4eac31512e15f538fc2bdfdc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x812591fc0c9434c2e7ac4ab9654ace26808907c7e6a4481568a2a5fb47f0ca29", + "blockNumber": "0x3c0", + "blockTimestamp": "0x6a5e002c", + "transactionHash": "0x3e3e995f9bf0770cf237b6c9b669565b3a48d91b4eac31512e15f538fc2bdfdc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bddd3cf97a12ae4039e336725a17afecedd20fa805adcf647e3125e00708204bbff", + "blockHash": "0x7a17866a2523f2ebfdb3eebf5871cf4550486affe9e025735340eddf1f3c1d56", + "blockNumber": "0x3c5", + "blockTimestamp": "0x6a5e002c", + "transactionHash": "0x24924baf6996d9dc1b6ee52ffe303ab937307f2ab8a007190f39237b4690b9f8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7a17866a2523f2ebfdb3eebf5871cf4550486affe9e025735340eddf1f3c1d56", + "blockNumber": "0x3c5", + "blockTimestamp": "0x6a5e002c", + "transactionHash": "0x24924baf6996d9dc1b6ee52ffe303ab937307f2ab8a007190f39237b4690b9f8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xd3cf97a12ae4039e336725a17afecedd20fa805adcf647e3125e00708204bbff0acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66", + "blockHash": "0xbd27616d1b31980a38ff50a44bfd58c8bcb20d555d61620ca91e0062efc19f42", + "blockNumber": "0x3c6", + "blockTimestamp": "0x6a5e002d", + "transactionHash": "0x88014f4898e0235ed3717be7c2d353e2972693b3909ffbed1d4454bec7b2c7d9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbd27616d1b31980a38ff50a44bfd58c8bcb20d555d61620ca91e0062efc19f42", + "blockNumber": "0x3c6", + "blockTimestamp": "0x6a5e002d", + "transactionHash": "0x88014f4898e0235ed3717be7c2d353e2972693b3909ffbed1d4454bec7b2c7d9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x0acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66f56fdc4a7beee527bddd4ec31cfaf265c32005b1f047fb5f06d9504ab1dc2310", + "blockHash": "0xa98e76d33a79946f8dde8d92fc7c03a64bbe1214a96ff9597e6170925000a11c", + "blockNumber": "0x3cb", + "blockTimestamp": "0x6a5e002e", + "transactionHash": "0x5bc7ecebf2f561c9c7c0cae50b7e8239d71351052d3a4bfb01e4b171d38dbd17", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa98e76d33a79946f8dde8d92fc7c03a64bbe1214a96ff9597e6170925000a11c", + "blockNumber": "0x3cb", + "blockTimestamp": "0x6a5e002e", + "transactionHash": "0x5bc7ecebf2f561c9c7c0cae50b7e8239d71351052d3a4bfb01e4b171d38dbd17", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xf56fdc4a7beee527bddd4ec31cfaf265c32005b1f047fb5f06d9504ab1dc23101beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590", + "blockHash": "0x533595c2344830df441992a023b4d8c460e02481b639e6e617fd032c56eb71c9", + "blockNumber": "0x3cc", + "blockTimestamp": "0x6a5e002f", + "transactionHash": "0x5e8f19192649fa0544c118dff1c8bed9c3599d3637a33e6986af98a361b755d9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x533595c2344830df441992a023b4d8c460e02481b639e6e617fd032c56eb71c9", + "blockNumber": "0x3cc", + "blockTimestamp": "0x6a5e002f", + "transactionHash": "0x5e8f19192649fa0544c118dff1c8bed9c3599d3637a33e6986af98a361b755d9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x1beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590f7f66c2b6fe771989bc306c376be13fbd4ff851f8a0cd8a4ba698d34958c9bd5", + "blockHash": "0x06d6a1afc9649c20c41209726ec753e7067f93eed0cf9be8adb5a20ee4ecb014", + "blockNumber": "0x3d1", + "blockTimestamp": "0x6a5e0030", + "transactionHash": "0x570f8335b3b925c0748b6b47f93363cf793164bb692e43b8e46ba41eda358597", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x06d6a1afc9649c20c41209726ec753e7067f93eed0cf9be8adb5a20ee4ecb014", + "blockNumber": "0x3d1", + "blockTimestamp": "0x6a5e0030", + "transactionHash": "0x570f8335b3b925c0748b6b47f93363cf793164bb692e43b8e46ba41eda358597", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xf7f66c2b6fe771989bc306c376be13fbd4ff851f8a0cd8a4ba698d34958c9bd5f537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca579", + "blockHash": "0xb44bbb486c2be2333c0b3b2626a0a8590814ab9c03cbd4d786a8e21152584a15", + "blockNumber": "0x3d2", + "blockTimestamp": "0x6a5e0031", + "transactionHash": "0x99b406ec1bae042698a7a8cbbe5c161ff1b16e45a60ca8f1eb67dee1f3206c69", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb44bbb486c2be2333c0b3b2626a0a8590814ab9c03cbd4d786a8e21152584a15", + "blockNumber": "0x3d2", + "blockTimestamp": "0x6a5e0031", + "transactionHash": "0x99b406ec1bae042698a7a8cbbe5c161ff1b16e45a60ca8f1eb67dee1f3206c69", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xf537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca5791dd73928b7937ceb5528c386aa29107602977e8ea68f32ae63ba82c1aa34b7e0", + "blockHash": "0x5404cf0103a5e246354746a294410823690ab4e9ec32b54686ae199947dedb3d", + "blockNumber": "0x3d7", + "blockTimestamp": "0x6a5e0031", + "transactionHash": "0xe2964bf33c3324e98fa61f3fd44d610a964a2a9e3c6bf46db44736672ab27b7f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5404cf0103a5e246354746a294410823690ab4e9ec32b54686ae199947dedb3d", + "blockNumber": "0x3d7", + "blockTimestamp": "0x6a5e0031", + "transactionHash": "0xe2964bf33c3324e98fa61f3fd44d610a964a2a9e3c6bf46db44736672ab27b7f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x1dd73928b7937ceb5528c386aa29107602977e8ea68f32ae63ba82c1aa34b7e076b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c5", + "blockHash": "0x7ba12d2dd7635845aec63a6bf05665924047834a3d7db45b3d8b2959b05576e4", + "blockNumber": "0x3d8", + "blockTimestamp": "0x6a5e0032", + "transactionHash": "0x321a95c8e9dbde9f2d379cac012c30ee0029d15842f4ec454b0898aa3f94d2e8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7ba12d2dd7635845aec63a6bf05665924047834a3d7db45b3d8b2959b05576e4", + "blockNumber": "0x3d8", + "blockTimestamp": "0x6a5e0032", + "transactionHash": "0x321a95c8e9dbde9f2d379cac012c30ee0029d15842f4ec454b0898aa3f94d2e8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x76b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c54b342f56cabe93e0cd5ffb8e77ceb1d4b922f4b31db9fce4bbeb9114a5a8bfc2", + "blockHash": "0x5d57ba00e10f1d57b33d908662e5aec75bc0ecef1e3ee520e773c1756ca89195", + "blockNumber": "0x3dd", + "blockTimestamp": "0x6a5e0033", + "transactionHash": "0xefadd41e12c08e0fdb15c911ba49ed17f8bef6047fe5b38270c66995de70942a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5d57ba00e10f1d57b33d908662e5aec75bc0ecef1e3ee520e773c1756ca89195", + "blockNumber": "0x3dd", + "blockTimestamp": "0x6a5e0033", + "transactionHash": "0xefadd41e12c08e0fdb15c911ba49ed17f8bef6047fe5b38270c66995de70942a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x4b342f56cabe93e0cd5ffb8e77ceb1d4b922f4b31db9fce4bbeb9114a5a8bfc23bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb", + "blockHash": "0xf29d74b1187422e7df6b66ccb5011bba7ee0dc5ffceed5cc1f174d54c17ce532", + "blockNumber": "0x3de", + "blockTimestamp": "0x6a5e0034", + "transactionHash": "0x7fc75d66e8c501290c50ad2770caefc845ea08c02321392baf68a033c0c0bd10", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf29d74b1187422e7df6b66ccb5011bba7ee0dc5ffceed5cc1f174d54c17ce532", + "blockNumber": "0x3de", + "blockTimestamp": "0x6a5e0034", + "transactionHash": "0x7fc75d66e8c501290c50ad2770caefc845ea08c02321392baf68a033c0c0bd10", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x3bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb2b1eabedaed2df1c497a6f5587c2ba10d02e37597caec99bba5eb23c9f6547f3", + "blockHash": "0x668163db1af212a34b5880c7301a6db42008ccddca930906347799b46299a113", + "blockNumber": "0x3e3", + "blockTimestamp": "0x6a5e0035", + "transactionHash": "0xb0dd1bd6c756dbdda64b4c29e555280feb5635c09bb9696480492568ad53dc67", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x668163db1af212a34b5880c7301a6db42008ccddca930906347799b46299a113", + "blockNumber": "0x3e3", + "blockTimestamp": "0x6a5e0035", + "transactionHash": "0xb0dd1bd6c756dbdda64b4c29e555280feb5635c09bb9696480492568ad53dc67", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0x2b1eabedaed2df1c497a6f5587c2ba10d02e37597caec99bba5eb23c9f6547f3c6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab", + "blockHash": "0x397729cad441022089fce6d3825fe2e8a5cb727ed8861ba1e5bb5c4ab7e411db", + "blockNumber": "0x3e4", + "blockTimestamp": "0x6a5e0036", + "transactionHash": "0x7eafcddf5d1af9cbde9f6440173cb791f8f786131be8380d6f1bf0a2df9c43d0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x397729cad441022089fce6d3825fe2e8a5cb727ed8861ba1e5bb5c4ab7e411db", + "blockNumber": "0x3e4", + "blockTimestamp": "0x6a5e0036", + "transactionHash": "0x7eafcddf5d1af9cbde9f6440173cb791f8f786131be8380d6f1bf0a2df9c43d0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d" + ], + "data": "0xc6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab92ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0xeb7d9db4dda3f184cef9cb698850bceb8c2272d7bb443d6ce7de138d8f704ae6", + "blockNumber": "0x3e9", + "blockTimestamp": "0x6a5e0036", + "transactionHash": "0x1827563dc36245a987574f79a81d544addfd006d94e3b44386e81c425def3582", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000954b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xeb7d9db4dda3f184cef9cb698850bceb8c2272d7bb443d6ce7de138d8f704ae6", + "blockNumber": "0x3e9", + "blockTimestamp": "0x6a5e0036", + "transactionHash": "0x1827563dc36245a987574f79a81d544addfd006d94e3b44386e81c425def3582", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d", + "0x00000000000000000000000098bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865" + ], + "data": "0x", + "blockHash": "0xb23121df012cdb41cd9cd892c8d279a0db4279c1d2d3b7f96567d49bdda6d181", + "blockNumber": "0x3ea", + "blockTimestamp": "0x6a5e0037", + "transactionHash": "0xdfe1e41924d649753f6c0fb437831e17dde4e18dcacfe58ad418c9d1eb702d72", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000026b44800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb23121df012cdb41cd9cd892c8d279a0db4279c1d2d3b7f96567d49bdda6d181", + "blockNumber": "0x3ea", + "blockTimestamp": "0x6a5e0037", + "transactionHash": "0xdfe1e41924d649753f6c0fb437831e17dde4e18dcacfe58ad418c9d1eb702d72", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc92ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0x774ed238b0c40f2e8b88bf16f822af982381b05eb57131f2607f77eeffb6d7e1", + "blockNumber": "0x3ef", + "blockTimestamp": "0x6a5e0038", + "transactionHash": "0x65d830cd2a6f068adfb8ef9023534ace14f5a78f4fb77c2f48d460e55e3e1202", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0xb61892243ef9d1ff631a3c5101d9cfb118d2e7ccff54b180140357402368add60000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0900949479a64003c840ec740413c0d484f2b260ef5f0ca96bf0edb420dc2bf5", + "blockNumber": "0x3f0", + "blockTimestamp": "0x6a5e0038", + "transactionHash": "0xefbc3ecd2100f577684205d9afb80fb71196394d78e3d69446d713b9caca83bc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d", + "0x4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc", + "0xb61892243ef9d1ff631a3c5101d9cfb118d2e7ccff54b180140357402368add6" + ], + "data": "0x260116ebf7233a663e2723e7f47212fb6b6a59939e29000f6e84db38f1e4f18a", + "blockHash": "0x0900949479a64003c840ec740413c0d484f2b260ef5f0ca96bf0edb420dc2bf5", + "blockNumber": "0x3f0", + "blockTimestamp": "0x6a5e0038", + "transactionHash": "0xefbc3ecd2100f577684205d9afb80fb71196394d78e3d69446d713b9caca83bc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0xe7f98f412497fc8091f9ae9d9254c1107cd574739f7ac9ddf78e570fdbc3db91693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5", + "blockHash": "0xe176e33ae9dd1471c42649eba4066c1ac19d959400f47114d132d3187b697ba3", + "blockNumber": "0x3f1", + "blockTimestamp": "0x6a5e0039", + "transactionHash": "0x6ac9718e6bc2aaf2b54a5b44a5059b79e69861340c85400ad1658390dc578ea7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe176e33ae9dd1471c42649eba4066c1ac19d959400f47114d132d3187b697ba3", + "blockNumber": "0x3f1", + "blockTimestamp": "0x6a5e0039", + "transactionHash": "0x6ac9718e6bc2aaf2b54a5b44a5059b79e69861340c85400ad1658390dc578ea7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x693fc834482bbd66938536b10dbb30dc12be4548eb13badcd688ed639f8177b5a5ccd9aed509f22289dad739a70c64bac45282d709182320875523aae28f1bae", + "blockHash": "0xe2a3ab2ac0abaed205c0c7a2be07c69a57503a092376b2fe49a3f423ef908225", + "blockNumber": "0x3f6", + "blockTimestamp": "0x6a5e003a", + "transactionHash": "0x0285fd08e92eb207718b23c221a1657402a1ba527dcafc5742104494c7fdda5a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe2a3ab2ac0abaed205c0c7a2be07c69a57503a092376b2fe49a3f423ef908225", + "blockNumber": "0x3f6", + "blockTimestamp": "0x6a5e003a", + "transactionHash": "0x0285fd08e92eb207718b23c221a1657402a1ba527dcafc5742104494c7fdda5a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x42522c212dbda09a5325d64352429f3e0021b065b8c0999ad7b6a750cc5453a12cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bdd", + "blockHash": "0x7a70c3d53a45361efbeda9abf60ec4a5bb99748086ff9799767ba8ec9c99afd7", + "blockNumber": "0x3f7", + "blockTimestamp": "0x6a5e003b", + "transactionHash": "0x39b07fef330736682a20083c35404c33f2eec27f5e16aa54ffc2a7c7d657d8ba", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7a70c3d53a45361efbeda9abf60ec4a5bb99748086ff9799767ba8ec9c99afd7", + "blockNumber": "0x3f7", + "blockTimestamp": "0x6a5e003b", + "transactionHash": "0x39b07fef330736682a20083c35404c33f2eec27f5e16aa54ffc2a7c7d657d8ba", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x2cb8e38b0be66bdc9efb7db260078a50cc368d20c476ed2440c567303bdd7bddd2cb250d317c20301f19dba9aa752c9c0aea7f939d00e825539dc2bc589b799c", + "blockHash": "0x0685d3778e4d8e066311b0704dfe53567f695bc38ff8bb516f447795a9d32ece", + "blockNumber": "0x3fc", + "blockTimestamp": "0x6a5e003c", + "transactionHash": "0x96d47f8673b3f3016b8f4f8a160ae334a44f485eac205f97668e1b6a14dca35c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0685d3778e4d8e066311b0704dfe53567f695bc38ff8bb516f447795a9d32ece", + "blockNumber": "0x3fc", + "blockTimestamp": "0x6a5e003c", + "transactionHash": "0x96d47f8673b3f3016b8f4f8a160ae334a44f485eac205f97668e1b6a14dca35c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x07107df87a6c9706401de212f253001239c0558e5458b50bfad856965851f6420acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66", + "blockHash": "0x6753e9752e790891a94c5ea938fcd60e62aa953b967ac6a29b47c4265f20cb86", + "blockNumber": "0x3fd", + "blockTimestamp": "0x6a5e003d", + "transactionHash": "0x39a1310c439f382f4ee9d2f3929dd9bd06a1f1affeb887d9ca2b709e51ebbfd1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6753e9752e790891a94c5ea938fcd60e62aa953b967ac6a29b47c4265f20cb86", + "blockNumber": "0x3fd", + "blockTimestamp": "0x6a5e003d", + "transactionHash": "0x39a1310c439f382f4ee9d2f3929dd9bd06a1f1affeb887d9ca2b709e51ebbfd1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x0acd691c91f095a86ce38040f43ecd291b2645c35c2a43309d9bd7633f65dc66f254da09d7ffb13ab8a5e703b529a371bf045829f5b0503f52c0dc6920da715a", + "blockHash": "0x7b84997521a92e0ef86f31dd4fb7c5d21ad7c213e60de8f911d2fba86c62a709", + "blockNumber": "0x402", + "blockTimestamp": "0x6a5e003e", + "transactionHash": "0xc9928c77d4a4e9e94f02b0504476ca968dfa2fa32337add27f97688b61eaf3ef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7b84997521a92e0ef86f31dd4fb7c5d21ad7c213e60de8f911d2fba86c62a709", + "blockNumber": "0x402", + "blockTimestamp": "0x6a5e003e", + "transactionHash": "0xc9928c77d4a4e9e94f02b0504476ca968dfa2fa32337add27f97688b61eaf3ef", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x579d27ce73111e4e8e0ac3301146fdfa71544e1fa1fc5b83595ebe888f9224731beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590", + "blockHash": "0x769de577d07e379ee7fece50fbb859c6c0107cd7d9418456dff00c0ec5ce742e", + "blockNumber": "0x403", + "blockTimestamp": "0x6a5e003f", + "transactionHash": "0x6e0378c3f87993b806977066dce31aab13c02767705b0a8b4c128582fc0c9ef0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x769de577d07e379ee7fece50fbb859c6c0107cd7d9418456dff00c0ec5ce742e", + "blockNumber": "0x403", + "blockTimestamp": "0x6a5e003f", + "transactionHash": "0x6e0378c3f87993b806977066dce31aab13c02767705b0a8b4c128582fc0c9ef0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x1beb34df34799fe5d220e5361dbd401aeecbe3ed367b0f64bd079fb70d53e590144f52e8c3ee31a01f6baf708548ad91d7be09b790c19f9607859b2090d9d31e", + "blockHash": "0xbdba9dc4cc2c66027ce8efdf7d54d86aaeff3d252c3c884496f100a876150398", + "blockNumber": "0x408", + "blockTimestamp": "0x6a5e0040", + "transactionHash": "0x190057356ea43a4404638811733a24bff5b1ff57c90b8bb21ba7c1470e886d0d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbdba9dc4cc2c66027ce8efdf7d54d86aaeff3d252c3c884496f100a876150398", + "blockNumber": "0x408", + "blockTimestamp": "0x6a5e0040", + "transactionHash": "0x190057356ea43a4404638811733a24bff5b1ff57c90b8bb21ba7c1470e886d0d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0xf7779b05d120613d9c9cbf35e8c7aab418d9bf6f35e3a52c422f3dda3102d495f537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca579", + "blockHash": "0x0e85b2a4336fe21d1722c000bfb6a4b0baaecb12ea350785d6501a1356a29c6f", + "blockNumber": "0x409", + "blockTimestamp": "0x6a5e0041", + "transactionHash": "0x82bfd289eff9f06d36ff444fd058b6f3e76ee3ebbf263e091dacddc0f7289f0e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0e85b2a4336fe21d1722c000bfb6a4b0baaecb12ea350785d6501a1356a29c6f", + "blockNumber": "0x409", + "blockTimestamp": "0x6a5e0041", + "transactionHash": "0x82bfd289eff9f06d36ff444fd058b6f3e76ee3ebbf263e091dacddc0f7289f0e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0xf537021e332d748e222b59b578bda167be1201d30f2938174b9300a8a78ca5799a641c5e401bfcfb6671fdef0ca942b6dc92d1009fe18a668ca20611fbd3cdd4", + "blockHash": "0x115c0c4d3d31c3306f20d212b000cfe29b1599df3bbca2427da045057a6fd2e6", + "blockNumber": "0x40e", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x0e53ef1f6ebc71c49f266de48eb2b3ebd4d6e2b19dc967d89a327a1172e0cdb8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x115c0c4d3d31c3306f20d212b000cfe29b1599df3bbca2427da045057a6fd2e6", + "blockNumber": "0x40e", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x0e53ef1f6ebc71c49f266de48eb2b3ebd4d6e2b19dc967d89a327a1172e0cdb8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0xb6f3e4028d0dac3ac7e589b780b3e8c08c2cd96e77cf419295df51be28c63f2c76b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c5", + "blockHash": "0x2b7e9226540715b15b1a1e16bb391e77d9fac9b2d601fa3abb89a4e049bbae81", + "blockNumber": "0x40f", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x6f7bb6feab1067eb383bfc37eacf2259bfa649daa0622838d7c546e5705ad1bf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2b7e9226540715b15b1a1e16bb391e77d9fac9b2d601fa3abb89a4e049bbae81", + "blockNumber": "0x40f", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x6f7bb6feab1067eb383bfc37eacf2259bfa649daa0622838d7c546e5705ad1bf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x76b8f61ad413461ab821704bc766e1417a57d94e9dd2494f033b104214e505c545a14b533f220ee97ef7091b2ce68ddc4f97c781b6869a1bbe8b225dd00a4bfc", + "blockHash": "0xcbad928da774f748866238890e17ae4e82e51a87213d51d15e786a2c1478feec", + "blockNumber": "0x410", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x374acd7246c6d21fd592e6cae40644fe18eef84619b789edc9f816148f649f94", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcbad928da774f748866238890e17ae4e82e51a87213d51d15e786a2c1478feec", + "blockNumber": "0x410", + "blockTimestamp": "0x6a5e0043", + "transactionHash": "0x374acd7246c6d21fd592e6cae40644fe18eef84619b789edc9f816148f649f94", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x99a95481ce5f39a4c4f68ab953af67b4db7b4f038e597d92b317fb707deb71123bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb", + "blockHash": "0x8b4b75dad8b651cc0d5a01dcac3700ef9cd3fa148ac67716a7e3b64de0acb72c", + "blockNumber": "0x411", + "blockTimestamp": "0x6a5e0044", + "transactionHash": "0x76632abf9875081958bff061b522635162e552789851bf240dd8a3cc3cd60a11", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8b4b75dad8b651cc0d5a01dcac3700ef9cd3fa148ac67716a7e3b64de0acb72c", + "blockNumber": "0x411", + "blockTimestamp": "0x6a5e0044", + "transactionHash": "0x76632abf9875081958bff061b522635162e552789851bf240dd8a3cc3cd60a11", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x3bf254485ba53879d1404e58d2a9000bdc950e903dc641ce284ce6be30b6aaeb68cd35acb3d6a699225e69c92da70a35541a726261ce9baa09d5caee807c5401", + "blockHash": "0x059f660433cfb77b6cd8644dc15e302a1f9cddda2104f949c20b3e7490d3ac86", + "blockNumber": "0x416", + "blockTimestamp": "0x6a5e0045", + "transactionHash": "0x37d8f71c53b7521350d79fb92a6da3af21aadbacb57acc22e28c701733ced20b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x059f660433cfb77b6cd8644dc15e302a1f9cddda2104f949c20b3e7490d3ac86", + "blockNumber": "0x416", + "blockTimestamp": "0x6a5e0045", + "transactionHash": "0x37d8f71c53b7521350d79fb92a6da3af21aadbacb57acc22e28c701733ced20b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0x9e8abaeb6cf0ea8205e98968ac049d2e011b985e32756a5d7e3d6000cc595337c6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab", + "blockHash": "0xf7a6bf26214e15f850c68d9e98b3b33abe3b44bb798e20697a79e199c583bfae", + "blockNumber": "0x417", + "blockTimestamp": "0x6a5e0046", + "transactionHash": "0x8572204804d9842ee85fc02f62ad62e319264ef25cb92647fbd5b4585cf3bc80", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf7a6bf26214e15f850c68d9e98b3b33abe3b44bb798e20697a79e199c583bfae", + "blockNumber": "0x417", + "blockTimestamp": "0x6a5e0046", + "transactionHash": "0x8572204804d9842ee85fc02f62ad62e319264ef25cb92647fbd5b4585cf3bc80", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d" + ], + "data": "0xc6ea962283a98fb27a36440d33302472e2e7d14d00cc4b8853e6b752004049ab92ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0x00da84b1278061eb9b37661506f367855210a1b04fb0e98a3121b1df77fec89f", + "blockNumber": "0x41c", + "blockTimestamp": "0x6a5e0047", + "transactionHash": "0x9ea41da38acbabb461087251cbeb1c88d17faf1c28bd8f756e4fd3f1c3125860", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x00da84b1278061eb9b37661506f367855210a1b04fb0e98a3121b1df77fec89f", + "blockNumber": "0x41c", + "blockTimestamp": "0x6a5e0047", + "transactionHash": "0x9ea41da38acbabb461087251cbeb1c88d17faf1c28bd8f756e4fd3f1c3125860", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d", + "0x000000000000000000000000d89b879abf354b3912f764fbd597c51d6e33340d" + ], + "data": "0x", + "blockHash": "0x4a9ebaa5c5ddd1c056ee3ea0ba8ef7f028d55d8e12a6cf6f00ee69172eb1c672", + "blockNumber": "0x41d", + "blockTimestamp": "0x6a5e0048", + "transactionHash": "0x162a284daf6d853297e503affde559c94cff534413e7c2314c89ab255fc463af", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000002582b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4a9ebaa5c5ddd1c056ee3ea0ba8ef7f028d55d8e12a6cf6f00ee69172eb1c672", + "blockNumber": "0x41d", + "blockTimestamp": "0x6a5e0048", + "transactionHash": "0x162a284daf6d853297e503affde559c94cff534413e7c2314c89ab255fc463af", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea980000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe9f5e954bd829fb5460532b606a7e3d279f203dd6f95503c9daae2d7a655cc8e", + "blockNumber": "0x422", + "blockTimestamp": "0x6a5e0049", + "transactionHash": "0x0451d732dba9673e4a57dfa5bc241050a27464f010f528e6e89f7d2c202b63e7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f92ce952f842e2cdea362902ec797688c326da756197aa76f1a1f622dac6c27b5", + "blockHash": "0xfd262aa8b2f955100932c3e2cda5b0e59b0756219f76e43ac4b7852bb42feded", + "blockNumber": "0x423", + "blockTimestamp": "0x6a5e004a", + "transactionHash": "0x95224bb899d87af3e1b0e4d7a98bba9c146562fe60904e52ced8efdca7c20413", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405", + "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea98", + "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f" + ], + "data": "0x54096d0c27c37e34edb153cb201c6355ca2795f64ac479d63720b155683143dd", + "blockHash": "0xfd262aa8b2f955100932c3e2cda5b0e59b0756219f76e43ac4b7852bb42feded", + "blockNumber": "0x423", + "blockTimestamp": "0x6a5e004a", + "transactionHash": "0x95224bb899d87af3e1b0e4d7a98bba9c146562fe60904e52ced8efdca7c20413", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x54096d0c27c37e34edb153cb201c6355ca2795f64ac479d63720b155683143dd803566ac0efad943268cec89179e8eaff8f44cfef6a0a979e4b4f28efbec24b3", + "blockHash": "0x6cecf832804f92634d46a2aba8eb72ee50f7c4c52065c1ce8f65e21613ea6be3", + "blockNumber": "0x424", + "blockTimestamp": "0x6a5e004a", + "transactionHash": "0x6d015c3e07f82a2d6bed71be45a7e06d71c604bb0055676c44c3e952e7d4a810", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6cecf832804f92634d46a2aba8eb72ee50f7c4c52065c1ce8f65e21613ea6be3", + "blockNumber": "0x424", + "blockTimestamp": "0x6a5e004a", + "transactionHash": "0x6d015c3e07f82a2d6bed71be45a7e06d71c604bb0055676c44c3e952e7d4a810", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x3595d6fe2ca30f18950d6123a4dceb31d052f31bc1737bd085c8ee91ccf0f74b2dc430891e3018aaf0671931f3a2c2eb9f4487d3443be5aabaab048180616375", + "blockHash": "0x25c6e9c900250ae48db0ab9a8dc390296e4dc898c5ea347685b1b7c4e69f2b33", + "blockNumber": "0x425", + "blockTimestamp": "0x6a5e004b", + "transactionHash": "0x1591ddf30c2e0d9aab990daec69b98c0ebc5697d5caa590ed60cf95788da1232", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x25c6e9c900250ae48db0ab9a8dc390296e4dc898c5ea347685b1b7c4e69f2b33", + "blockNumber": "0x425", + "blockTimestamp": "0x6a5e004b", + "transactionHash": "0x1591ddf30c2e0d9aab990daec69b98c0ebc5697d5caa590ed60cf95788da1232", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x2dc430891e3018aaf0671931f3a2c2eb9f4487d3443be5aabaab048180616375bc5b67600c3e2a1cbbdc7f7fda6883e6716e220f27f96ee6df7bd7868fa99e96", + "blockHash": "0xdc3f2ed8c465b99733eb7afb173d660f7fd02109d4c7557b9029febb8f0819c2", + "blockNumber": "0x426", + "blockTimestamp": "0x6a5e004b", + "transactionHash": "0x9d24b6b1958c4b05bd4fabdfa027e4ebca84bd4de325192575dc9e153eb476e6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdc3f2ed8c465b99733eb7afb173d660f7fd02109d4c7557b9029febb8f0819c2", + "blockNumber": "0x426", + "blockTimestamp": "0x6a5e004b", + "transactionHash": "0x9d24b6b1958c4b05bd4fabdfa027e4ebca84bd4de325192575dc9e153eb476e6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xcce5364b1385663aa48abe01e9b564ab82e2d91fc1195808885556e7a10bd64b9e0f3af8d498a0f18b433368b0f2b4e3e6d3a8c41f1e5635e12e95910e895631", + "blockHash": "0xc48db9e9ab030c6d243d26f152c95c19efb3b01d158025c02558c6a8f559f37a", + "blockNumber": "0x427", + "blockTimestamp": "0x6a5e004c", + "transactionHash": "0xcea7e11e1f19fe8ae738231061ea7a06140251f755bcfbcca22e9a10ca13596a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc48db9e9ab030c6d243d26f152c95c19efb3b01d158025c02558c6a8f559f37a", + "blockNumber": "0x427", + "blockTimestamp": "0x6a5e004c", + "transactionHash": "0xcea7e11e1f19fe8ae738231061ea7a06140251f755bcfbcca22e9a10ca13596a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x9e0f3af8d498a0f18b433368b0f2b4e3e6d3a8c41f1e5635e12e95910e895631839a910db72070dfaecf39760eb3470c656bb32ffe8ed10f4b5ac751b0d0db39", + "blockHash": "0x64bfe1a1f053c8ae474297a70cd851830c60ed6da5517cc83f5836f87990c497", + "blockNumber": "0x42c", + "blockTimestamp": "0x6a5e004d", + "transactionHash": "0x01f22c4e337fd5b842c57849979a4603491626f248938562c72ae75aa2411c29", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x64bfe1a1f053c8ae474297a70cd851830c60ed6da5517cc83f5836f87990c497", + "blockNumber": "0x42c", + "blockTimestamp": "0x6a5e004d", + "transactionHash": "0x01f22c4e337fd5b842c57849979a4603491626f248938562c72ae75aa2411c29", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x2e9aba54aa9c6e6bb737d9323665c747da52c5136865d55ee47c488f628c794a828a434cbae3a7dca6ca1c785e76863c6900914543445652f252c203618bbac6", + "blockHash": "0xbb8a8b274763c00d6b65191d6307357358bc4dfb2788930b2a3c8ebbbcb843fb", + "blockNumber": "0x42d", + "blockTimestamp": "0x6a5e004e", + "transactionHash": "0x05fdfb7d717bed1d1b4bb875394d4ecc829e1e77fb0db2b82f7d7bffdd3c149d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbb8a8b274763c00d6b65191d6307357358bc4dfb2788930b2a3c8ebbbcb843fb", + "blockNumber": "0x42d", + "blockTimestamp": "0x6a5e004e", + "transactionHash": "0x05fdfb7d717bed1d1b4bb875394d4ecc829e1e77fb0db2b82f7d7bffdd3c149d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x828a434cbae3a7dca6ca1c785e76863c6900914543445652f252c203618bbac693ab62295ee9b4db758ea481e356642bd2bd220e83d9f5e1d1cc2bcf69fa2be0", + "blockHash": "0x1aba3211345bdf259108cd58b82fb91fcc2939c1e51b95771e1bc8806e3c3a09", + "blockNumber": "0x42e", + "blockTimestamp": "0x6a5e004e", + "transactionHash": "0x6e4b23e07d4d7136cfb58e0582a309b2a807039c5e2bad1f7a096ddfd1cc4d05", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1aba3211345bdf259108cd58b82fb91fcc2939c1e51b95771e1bc8806e3c3a09", + "blockNumber": "0x42e", + "blockTimestamp": "0x6a5e004e", + "transactionHash": "0x6e4b23e07d4d7136cfb58e0582a309b2a807039c5e2bad1f7a096ddfd1cc4d05", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x3cf34ec5071efdc1eb5ec91e654a14af5eab9a050bf87c512094d5db3aee3c4b2aa62fd9c1dfe06050772f5eaef455f51b2f03679d1cf5f8af5104a4a0807948", + "blockHash": "0xf3cd69b178272b53db8855015c413df0414403fcd6e4ccc5521aee6e486f42a0", + "blockNumber": "0x42f", + "blockTimestamp": "0x6a5e004f", + "transactionHash": "0x855bded6b7f45bc05740f3ea8d7da156c34fc02a498d9024b0b4b7eefb31f76b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf3cd69b178272b53db8855015c413df0414403fcd6e4ccc5521aee6e486f42a0", + "blockNumber": "0x42f", + "blockTimestamp": "0x6a5e004f", + "transactionHash": "0x855bded6b7f45bc05740f3ea8d7da156c34fc02a498d9024b0b4b7eefb31f76b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x9d291b20a852e9d4af501940b8e19969ac0fbed90bcdae36451d44c082f19ad564d0e2880953d9b60c2f482f9d9d6a64aded02129ed049a0c25fa21420d96802", + "blockHash": "0x4e3c345f51379f821668a750f873bf5254c45010aa1c45cf8928e1f1fd292d72", + "blockNumber": "0x430", + "blockTimestamp": "0x6a5e004f", + "transactionHash": "0xe63400000a2f105ebb834c6ac140dd88a060bdf3bf00081c36853dcf248c00da", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4e3c345f51379f821668a750f873bf5254c45010aa1c45cf8928e1f1fd292d72", + "blockNumber": "0x430", + "blockTimestamp": "0x6a5e004f", + "transactionHash": "0xe63400000a2f105ebb834c6ac140dd88a060bdf3bf00081c36853dcf248c00da", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x06ae7b9ac4461437b9ffb2405b77338553aa3197d240e0617c8291812f30283d87512fb070ca9952de59092821c74cff6d0e97fd3831ef887dcb7eacfd725c40", + "blockHash": "0x6b56d6b06170c70e06739b051f0ce9921b1352c77d2c1ec06c5ebbe3c90264c6", + "blockNumber": "0x431", + "blockTimestamp": "0x6a5e0050", + "transactionHash": "0x9de71ece5c167088a72cb5565db5d251ec4c1e08320815152232e6ee91e229e3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6b56d6b06170c70e06739b051f0ce9921b1352c77d2c1ec06c5ebbe3c90264c6", + "blockNumber": "0x431", + "blockTimestamp": "0x6a5e0050", + "transactionHash": "0x9de71ece5c167088a72cb5565db5d251ec4c1e08320815152232e6ee91e229e3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xb27a78ad4b8e4f34f9e2361573c78b9bff5318d025e86df1320d7ca7d772c272519e360d0e910a811fc62f434e9fc4c2b1c886e2975c66391fb7bdf296bf39ff", + "blockHash": "0xb9472b585e56eec2bf8a8e7e0f164741df8e7df8d6f69c55ce59697607633772", + "blockNumber": "0x436", + "blockTimestamp": "0x6a5e0051", + "transactionHash": "0xedee51edab345f0081bb5c5edabb9c125bc8c6b32636d9bc337d45987e9db17d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb9472b585e56eec2bf8a8e7e0f164741df8e7df8d6f69c55ce59697607633772", + "blockNumber": "0x436", + "blockTimestamp": "0x6a5e0051", + "transactionHash": "0xedee51edab345f0081bb5c5edabb9c125bc8c6b32636d9bc337d45987e9db17d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x373c311d64f4786fc0911628159294fba690586fe3d6fa5f0948086a2287b2690fa4132773012737970cd2050fd06499cd2b0d9a8454f3394915f29617cf0bc7", + "blockHash": "0x597e0c7f95b4238709dc7a4b7a8fe69f652c302a68a0fb873e3b67bb5c57ed96", + "blockNumber": "0x437", + "blockTimestamp": "0x6a5e0052", + "transactionHash": "0xe1e384033c12b5879e046ebc950ae643f680ab503e38169adfa1a08b3c348455", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x597e0c7f95b4238709dc7a4b7a8fe69f652c302a68a0fb873e3b67bb5c57ed96", + "blockNumber": "0x437", + "blockTimestamp": "0x6a5e0052", + "transactionHash": "0xe1e384033c12b5879e046ebc950ae643f680ab503e38169adfa1a08b3c348455", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xb008822674ed339d0bafbcfe46520c353e08058d0e1f3907a81954ea6e617f7fe182bcbbad187bacd23cd2fef39429e358e3c3808fd33e79a17d7ad1ef1b41e1", + "blockHash": "0x5535da9cad6e8c07150fc7b35571a320b8a9c9a53ed665e61b580fe9f24273b3", + "blockNumber": "0x438", + "blockTimestamp": "0x6a5e0052", + "transactionHash": "0x7720db16a4762a1884d52c79a6c5990d4490831000e31531352fa7f45bbf216f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5535da9cad6e8c07150fc7b35571a320b8a9c9a53ed665e61b580fe9f24273b3", + "blockNumber": "0x438", + "blockTimestamp": "0x6a5e0052", + "transactionHash": "0x7720db16a4762a1884d52c79a6c5990d4490831000e31531352fa7f45bbf216f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x6286ec24e41569f0f2aa89fffff6b7af553be16df5d15af19a654bf10aaeb4329e3d48048012e3ddc07eb10097e99ebeff4eec203737876295a1b895c79b417b", + "blockHash": "0xa804dc0c5032402aee16937664f5578549d2a6416c7aba6b7023b8d568a263a8", + "blockNumber": "0x439", + "blockTimestamp": "0x6a5e0053", + "transactionHash": "0x27c4f54df25712264032cbffdcbc4cd229a1c0f9943432aa78a67cccc47a8fa4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa804dc0c5032402aee16937664f5578549d2a6416c7aba6b7023b8d568a263a8", + "blockNumber": "0x439", + "blockTimestamp": "0x6a5e0053", + "transactionHash": "0x27c4f54df25712264032cbffdcbc4cd229a1c0f9943432aa78a67cccc47a8fa4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x6e19aa7ceadb51f0a00879060ae1ee7bbb2f9f3e3a3b534754a548e49c62fd3addb1a4ddca9186bac393487fceb0c59fdaac2a4caf97d2c46a95d331cfe57e6a", + "blockHash": "0xba937471803baa7d6ab86ea3f5828f06a7a8969eeada1a8962986fb40663a6a9", + "blockNumber": "0x43e", + "blockTimestamp": "0x6a5e0054", + "transactionHash": "0x36d0e563ae7e54ce94accb60a26d5082fc161285f680f30a5ae80b3db8fa87ff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xba937471803baa7d6ab86ea3f5828f06a7a8969eeada1a8962986fb40663a6a9", + "blockNumber": "0x43e", + "blockTimestamp": "0x6a5e0054", + "transactionHash": "0x36d0e563ae7e54ce94accb60a26d5082fc161285f680f30a5ae80b3db8fa87ff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x45a1739107b4dbf92d5f6481da603c7d2f95f29980b4704410a087e845b6953885678666f277ba62afc74662ccae59114a0110e379c410a532a3f8c05d6ed328", + "blockHash": "0xc58bee51be428454193157f77e8b50f66c44f0a31e31b282c3ec729f1c5f1a25", + "blockNumber": "0x43f", + "blockTimestamp": "0x6a5e0055", + "transactionHash": "0x9332b4b7c81a16a9210f204a53f0d3371d6723fd9f552dbaf24e5ed98a3b6a99", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc58bee51be428454193157f77e8b50f66c44f0a31e31b282c3ec729f1c5f1a25", + "blockNumber": "0x43f", + "blockTimestamp": "0x6a5e0055", + "transactionHash": "0x9332b4b7c81a16a9210f204a53f0d3371d6723fd9f552dbaf24e5ed98a3b6a99", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x030c411e2c5217b1690265450b1a66ae5471f09f344371701480989f4439c091a8372b84d77ec27fae23d63d0e4517f06f5d5259ee35447c83988523c21fda6e", + "blockHash": "0xa5226c1675554baa7976061af21154783203eb796a624b9bde1c93bca818a57e", + "blockNumber": "0x440", + "blockTimestamp": "0x6a5e0055", + "transactionHash": "0xe9d6ef2db76b9d3ac7711569b2fccf4985727e91be8bd4a36bc0ed0a1f97def3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa5226c1675554baa7976061af21154783203eb796a624b9bde1c93bca818a57e", + "blockNumber": "0x440", + "blockTimestamp": "0x6a5e0055", + "transactionHash": "0xe9d6ef2db76b9d3ac7711569b2fccf4985727e91be8bd4a36bc0ed0a1f97def3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x7abfeebd11e2064e1c967daa26145c075b56f9fe9720093c087c7958316f1ef846d5afedd6ac461ace476863046ad32bc0ac335e92df5cddd64d2d3ac9f1f3d7", + "blockHash": "0x7ec4db8b60950a232563b998cc3ed55277a1bc1a30f2e39dc47d9056f5a7eb03", + "blockNumber": "0x441", + "blockTimestamp": "0x6a5e0056", + "transactionHash": "0xd16aca94bfa80c8666047ad7591c0525e9bf752a922ce1ae8d8665da8d1ae49e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7ec4db8b60950a232563b998cc3ed55277a1bc1a30f2e39dc47d9056f5a7eb03", + "blockNumber": "0x441", + "blockTimestamp": "0x6a5e0056", + "transactionHash": "0xd16aca94bfa80c8666047ad7591c0525e9bf752a922ce1ae8d8665da8d1ae49e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xa47a10c1aad49f95193003ccf262071c8b87b772b1eb3c886c746e712a0b66a9fa5769e1177e05c9812def4dd31f4bc115f63b7be182dffb3e3bfcafd89119f7", + "blockHash": "0x5f5b0fd97f62d35a419b9ad692b00f65c4824d3f77fa8cdb457eb46a4b2068f0", + "blockNumber": "0x442", + "blockTimestamp": "0x6a5e0056", + "transactionHash": "0xdcd2d4c698ed4ee698ec1818c266252f092936d552ac789a71b6c926c2512404", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5f5b0fd97f62d35a419b9ad692b00f65c4824d3f77fa8cdb457eb46a4b2068f0", + "blockNumber": "0x442", + "blockTimestamp": "0x6a5e0056", + "transactionHash": "0xdcd2d4c698ed4ee698ec1818c266252f092936d552ac789a71b6c926c2512404", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x84496bb7c6381ed7e44227eae58af93d508db9d76f074ccbd94c23454b28966cb6e87bf73b08051f73f6069f544448c1af4838b17faf2bb043457751e6792052", + "blockHash": "0x9afdf20e24d1c4c30b69bfe249ec0694f4bcfa156b53c5e3b46666ee75c35690", + "blockNumber": "0x443", + "blockTimestamp": "0x6a5e0057", + "transactionHash": "0x5fedafc85c10870b101869c74752b6d9a51a2f3f21a5bc9073f7daf53b0fb554", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9afdf20e24d1c4c30b69bfe249ec0694f4bcfa156b53c5e3b46666ee75c35690", + "blockNumber": "0x443", + "blockTimestamp": "0x6a5e0057", + "transactionHash": "0x5fedafc85c10870b101869c74752b6d9a51a2f3f21a5bc9073f7daf53b0fb554", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x65cce5f810f002bfa8bc2b714d89cc3444d7bfb54f4e1229755eb404d8c9883724893e2c2f326ed19f515cd48d26ba6f9cd1f17e42cf2f06102e5111ddb31e15", + "blockHash": "0x4a3a7e8825e605b96cf87b8979a87fc895bda59c7cc9b39c270a6e21abd695bf", + "blockNumber": "0x448", + "blockTimestamp": "0x6a5e0058", + "transactionHash": "0x3a1517ce4d9903acbf71c982087503d014e0f8fe6dd3b8878e4fba511fabd826", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4a3a7e8825e605b96cf87b8979a87fc895bda59c7cc9b39c270a6e21abd695bf", + "blockNumber": "0x448", + "blockTimestamp": "0x6a5e0058", + "transactionHash": "0x3a1517ce4d9903acbf71c982087503d014e0f8fe6dd3b8878e4fba511fabd826", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xe6dd8d336aa0154ef5b3040f95361426d0082bc54a5a0d64fc10575c2a11c3311799dbaad8d517d8090a3430e7d55c62215415ff94bee51b16fb55574f58ef13", + "blockHash": "0x4e828577c8542a3dfcfe4bf12b7d6bc93c9d4fa2ca5d590d37204b7904042e7f", + "blockNumber": "0x449", + "blockTimestamp": "0x6a5e0059", + "transactionHash": "0xd99bc4293d704506cffd476a6be79121a619cc637993553e78f4d1cfae4b12ca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4e828577c8542a3dfcfe4bf12b7d6bc93c9d4fa2ca5d590d37204b7904042e7f", + "blockNumber": "0x449", + "blockTimestamp": "0x6a5e0059", + "transactionHash": "0xd99bc4293d704506cffd476a6be79121a619cc637993553e78f4d1cfae4b12ca", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x8f6271fe7ac7e829d9e5dfdffdf1fca1641fc6c01bdc1e296bcb2fef324410b9893aa23ab3140bb58955bce1dff4e10d31797ccc231b1a7d5c02316f1531e1e0", + "blockHash": "0x3f92700dbdcd77415e5cd358ccdddb4c0df3bfe8b98dc8828fac87e56217830f", + "blockNumber": "0x44a", + "blockTimestamp": "0x6a5e0059", + "transactionHash": "0xcda323685ef1b38c20fa98acdf5c7a60c1ed46d735c0f4d3ff9ea21f53cb066e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3f92700dbdcd77415e5cd358ccdddb4c0df3bfe8b98dc8828fac87e56217830f", + "blockNumber": "0x44a", + "blockTimestamp": "0x6a5e0059", + "transactionHash": "0xcda323685ef1b38c20fa98acdf5c7a60c1ed46d735c0f4d3ff9ea21f53cb066e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x5d67e35f6d838786797b5858b78ff255c84f6a7982b604fe07443814e7e73399d186edc317844e948a099af28c100aeb9ee9f2794bb2ea136418ea12294b6886", + "blockHash": "0xbd53e73cdead8e16722d93529113007b5ad4c8eee90363de4e9ebab51e89e6f2", + "blockNumber": "0x44b", + "blockTimestamp": "0x6a5e005a", + "transactionHash": "0xc9476268317d9deb00cb358301515226c2c328038804d5b5114f6205e76d816e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbd53e73cdead8e16722d93529113007b5ad4c8eee90363de4e9ebab51e89e6f2", + "blockNumber": "0x44b", + "blockTimestamp": "0x6a5e005a", + "transactionHash": "0xc9476268317d9deb00cb358301515226c2c328038804d5b5114f6205e76d816e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0xa07938f79190e3de4956e592d61ecc3b978a4adf21c5fe5ee696d7e26add7ff1356b4f59add06a396ccecaf6fb6489bc41731b49bb2f6868ce3740c57db1aee5", + "blockHash": "0x52174e87af01714876ad76cdab440123a0e62114fd17a805aaf7689037bec1ab", + "blockNumber": "0x450", + "blockTimestamp": "0x6a5e005c", + "transactionHash": "0x5565d3c70aa196813062ce407a4945a4aa17b36ff75af253ea013e04e71c6b3c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x52174e87af01714876ad76cdab440123a0e62114fd17a805aaf7689037bec1ab", + "blockNumber": "0x450", + "blockTimestamp": "0x6a5e005c", + "transactionHash": "0x5565d3c70aa196813062ce407a4945a4aa17b36ff75af253ea013e04e71c6b3c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405" + ], + "data": "0x3455688abf5b6f8b85f7d741c53c8e3f72aec8dbe0fc4c85cd8f56ef7d6d3e006f631eb4679195a4fea3f9c6fa883c36aa4392539d1e3a05465351dd7ff784e2", + "blockHash": "0x32156c7dfd4eb5f5f716ace075505189a49291d3c0a9890db17eaa81bd8a0134", + "blockNumber": "0x451", + "blockTimestamp": "0x6a5e005c", + "transactionHash": "0xa3be89d7f2b7c13fad1444ca6b448824dfc8b5ba36c6074aae8d3bb213228d7b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x32156c7dfd4eb5f5f716ace075505189a49291d3c0a9890db17eaa81bd8a0134", + "blockNumber": "0x451", + "blockTimestamp": "0x6a5e005c", + "transactionHash": "0xa3be89d7f2b7c13fad1444ca6b448824dfc8b5ba36c6074aae8d3bb213228d7b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2f8fd66be52dd0f97b5961e90b3cc91d02aaabdf1edd7e88543819366695eef1", + "blockNumber": "0x452", + "blockTimestamp": "0x6a5e005c", + "transactionHash": "0x2e899543d2cae4f7e10c5e6faf519d5aedf666d2e3e2b870d216c9f2178f5dd5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x7ba8b82881e719e60a1cc15303fcdda9dbdefba6291b7735f13a683655e82405", + "0xb164b211f16f799a14fea05e636c8dc3e79b185f1a4ddabdb63b99a1b444ea98", + "0x85cab1422de4ab8e98a207830eb933d55fa5b56101bc3ce63bfb8b995702385f" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0xa69ef65a72144ed6891fb6255b5e04fbeb33c193bb8a2207148f96561896d34f", + "blockNumber": "0x453", + "blockTimestamp": "0x6a5e005d", + "transactionHash": "0x9d3ffe854a575c691d2d5cd335c0ff055345bb6ffa115cda84d1a3c937947b4a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003854e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa69ef65a72144ed6891fb6255b5e04fbeb33c193bb8a2207148f96561896d34f", + "blockNumber": "0x453", + "blockTimestamp": "0x6a5e005d", + "transactionHash": "0x9d3ffe854a575c691d2d5cd335c0ff055345bb6ffa115cda84d1a3c937947b4a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x2ec7572a6a543817baf622af82cf24b7e1a92e8902864bb708b0845790ff316d", + "0x4025e56586beead6ca67dea3ee28d88dc498441d1202f702b258db9da70f4fbc", + "0xb61892243ef9d1ff631a3c5101d9cfb118d2e7ccff54b180140357402368add6" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xebb5107033642d88a87fe9e5f37bb2415fb5f455459b6e5673b71192b04403fa", + "blockNumber": "0x558", + "blockTimestamp": "0x6a5e0064", + "transactionHash": "0xdcd05f677fbae8c5269e6f37696c1356ade96bad89958d2abad4387543267478", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xebb5107033642d88a87fe9e5f37bb2415fb5f455459b6e5673b71192b04403fa", + "blockNumber": "0x558", + "blockTimestamp": "0x6a5e0064", + "transactionHash": "0xdcd05f677fbae8c5269e6f37696c1356ade96bad89958d2abad4387543267478", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xd894ffc08efac90ae88ec3b4d1f68f95fe0e7b0ad86158b04ca8a097b15a975d", + "0x4b5139c9af393fc5321c8a6a38e6172b2945cd5512c76146642ceb768e9f0a7a", + "0x361754da7a2b2f85b7c35b83aa161972b2eac1c1a3ae1eb0d31cfa9961b831e0" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0xd66fdcd8eb60a3156b0423997338a0d2f2c62948170969d6ce1e18c4b91d23b7", + "blockNumber": "0x559", + "blockTimestamp": "0x6a5e0065", + "transactionHash": "0x41e171248dbe6834e24bc8f48c73b3a09d8f7ffc16b220ecc578b78e05430ac7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000017d9f800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd66fdcd8eb60a3156b0423997338a0d2f2c62948170969d6ce1e18c4b91d23b7", + "blockNumber": "0x559", + "blockTimestamp": "0x6a5e0065", + "transactionHash": "0x41e171248dbe6834e24bc8f48c73b3a09d8f7ffc16b220ecc578b78e05430ac7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x7393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23fb413f01343d226b18b9263d210be2d8b97ee53d722bdc09d72ac3e2cd92aa7b", + "blockHash": "0x8cc1e53e4eb3f9293e4074a279dbf3ae274a465f70b5dd9962257286939ff487", + "blockNumber": "0x55a", + "blockTimestamp": "0x6a5e0066", + "transactionHash": "0x28eebbcb76ed9e422e606c4d503da09fd68f50054a15ac8706ad1ec8a3a405fa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x0000000000000000000000006ec4c9d2fd20ce509f40737561156c1091834121", + "blockHash": "0x239ec3246cc6931323e3c82c53154b76bc2b4665b516a0fede5f80a54c0f6fa0", + "blockNumber": "0x55b", + "blockTimestamp": "0x6a5e0066", + "transactionHash": "0xdd7eb9bf834d3b8f1a7618ee9f25b4e5d4666ef08365e2d8e74dfb2f75e79641", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x9da58d313a19d0185ff8ad3bad7e538d9e477697", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000047393d92386853c6820d09fd1348ed09e166101ab9de06c1767676f74c8f77c23fb413f01343d226b18b9263d210be2d8b97ee53d722bdc09d72ac3e2cd92aa7b0000000000000000000000006ec4c9d2fd20ce509f40737561156c1091834121", + "blockHash": "0x239ec3246cc6931323e3c82c53154b76bc2b4665b516a0fede5f80a54c0f6fa0", + "blockNumber": "0x55b", + "blockTimestamp": "0x6a5e0066", + "transactionHash": "0xdd7eb9bf834d3b8f1a7618ee9f25b4e5d4666ef08365e2d8e74dfb2f75e79641", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ] +} \ No newline at end of file diff --git a/cartesi-rollups/node/tests/fixtures/chain-recordings/multilevel_stf.json b/cartesi-rollups/node/tests/fixtures/chain-recordings/multilevel_stf.json new file mode 100644 index 000000000..b02fe07b2 --- /dev/null +++ b/cartesi-rollups/node/tests/fixtures/chain-recordings/multilevel_stf.json @@ -0,0 +1,13406 @@ +{ + "note": "epoch-4", + "chain_id": 31337, + "from_block": 0, + "to_block": 2089, + "block_timestamps": { + "23": 1784543861, + "24": 1784543861, + "27": 1784543861, + "28": 1784543861, + "29": 1784543861, + "32": 1784543863, + "33": 1784543863, + "419": 1784543875, + "420": 1784543875, + "423": 1784543885, + "424": 1784543885, + "429": 1784543889, + "431": 1784543890, + "432": 1784543890, + "434": 1784543891, + "435": 1784543892, + "437": 1784543893, + "438": 1784543893, + "440": 1784543894, + "441": 1784543894, + "443": 1784543895, + "444": 1784543895, + "446": 1784543896, + "447": 1784543897, + "449": 1784543898, + "450": 1784543898, + "452": 1784543899, + "453": 1784543899, + "455": 1784543900, + "456": 1784543900, + "458": 1784543901, + "459": 1784543901, + "461": 1784543902, + "462": 1784543903, + "464": 1784543904, + "465": 1784543904, + "467": 1784543905, + "468": 1784543905, + "470": 1784543906, + "471": 1784543906, + "473": 1784543907, + "474": 1784543908, + "476": 1784543908, + "477": 1784543909, + "479": 1784543910, + "480": 1784543910, + "482": 1784543911, + "483": 1784543911, + "485": 1784543912, + "486": 1784543912, + "488": 1784543913, + "489": 1784543914, + "491": 1784543915, + "492": 1784543915, + "494": 1784543916, + "495": 1784543916, + "497": 1784543917, + "498": 1784543917, + "500": 1784543918, + "501": 1784543919, + "502": 1784543919, + "504": 1784543920, + "506": 1784543921, + "507": 1784543922, + "509": 1784543922, + "510": 1784543923, + "512": 1784543924, + "513": 1784543925, + "515": 1784543925, + "516": 1784543926, + "518": 1784543927, + "519": 1784543927, + "521": 1784543928, + "522": 1784543929, + "524": 1784543929, + "525": 1784543930, + "527": 1784543931, + "528": 1784543932, + "530": 1784543932, + "531": 1784543933, + "533": 1784543933, + "534": 1784543935, + "536": 1784543935, + "537": 1784543937, + "539": 1784543938, + "540": 1784543938, + "542": 1784543939, + "543": 1784543940, + "545": 1784543941, + "546": 1784543941, + "548": 1784543942, + "549": 1784543943, + "551": 1784543944, + "552": 1784543945, + "554": 1784543946, + "555": 1784543946, + "557": 1784543947, + "558": 1784543948, + "560": 1784543949, + "561": 1784543950, + "563": 1784543950, + "564": 1784543951, + "566": 1784543952, + "567": 1784543953, + "569": 1784543954, + "570": 1784543954, + "572": 1784543955, + "573": 1784543956, + "575": 1784543957, + "576": 1784543958, + "577": 1784543958, + "578": 1784543958, + "835": 1784543963, + "836": 1784543964, + "837": 1784543965, + "838": 1784543965, + "841": 1784543974, + "842": 1784543974, + "847": 1784543977, + "849": 1784543978, + "850": 1784543978, + "852": 1784543979, + "853": 1784543980, + "855": 1784543980, + "856": 1784543981, + "858": 1784543981, + "859": 1784543982, + "861": 1784543983, + "862": 1784543983, + "864": 1784543984, + "865": 1784543985, + "867": 1784543985, + "868": 1784543986, + "870": 1784543986, + "871": 1784543987, + "873": 1784543987, + "874": 1784543988, + "876": 1784543989, + "877": 1784543989, + "879": 1784543990, + "880": 1784543991, + "882": 1784543991, + "883": 1784543992, + "885": 1784543992, + "886": 1784543993, + "888": 1784543994, + "889": 1784543994, + "891": 1784543995, + "892": 1784543996, + "894": 1784543996, + "895": 1784543997, + "897": 1784543997, + "898": 1784543998, + "900": 1784543998, + "901": 1784543999, + "903": 1784544000, + "904": 1784544000, + "906": 1784544001, + "907": 1784544002, + "909": 1784544002, + "910": 1784544003, + "912": 1784544003, + "913": 1784544004, + "915": 1784544004, + "916": 1784544005, + "918": 1784544006, + "919": 1784544007, + "921": 1784544007, + "922": 1784544008, + "924": 1784544008, + "925": 1784544009, + "927": 1784544010, + "928": 1784544011, + "930": 1784544011, + "931": 1784544012, + "933": 1784544012, + "934": 1784544014, + "936": 1784544014, + "937": 1784544015, + "939": 1784544015, + "940": 1784544017, + "942": 1784544017, + "943": 1784544018, + "945": 1784544018, + "946": 1784544019, + "948": 1784544020, + "949": 1784544023, + "951": 1784544024, + "952": 1784544024, + "954": 1784544025, + "955": 1784544026, + "957": 1784544026, + "958": 1784544027, + "960": 1784544028, + "961": 1784544029, + "963": 1784544030, + "964": 1784544031, + "966": 1784544031, + "967": 1784544032, + "969": 1784544033, + "970": 1784544034, + "972": 1784544034, + "973": 1784544036, + "975": 1784544036, + "976": 1784544037, + "978": 1784544038, + "979": 1784544039, + "981": 1784544039, + "982": 1784544041, + "984": 1784544041, + "985": 1784544042, + "987": 1784544043, + "988": 1784544044, + "990": 1784544044, + "991": 1784544045, + "993": 1784544046, + "994": 1784544047, + "995": 1784544047, + "996": 1784544047, + "1253": 1784544052, + "1254": 1784544053, + "1255": 1784544054, + "1256": 1784544054, + "1259": 1784544064, + "1260": 1784544064, + "1265": 1784544068, + "1267": 1784544068, + "1268": 1784544069, + "1270": 1784544069, + "1271": 1784544070, + "1273": 1784544070, + "1274": 1784544071, + "1276": 1784544071, + "1277": 1784544072, + "1279": 1784544073, + "1280": 1784544074, + "1282": 1784544074, + "1283": 1784544075, + "1285": 1784544075, + "1286": 1784544076, + "1288": 1784544076, + "1289": 1784544077, + "1291": 1784544077, + "1292": 1784544079, + "1294": 1784544079, + "1295": 1784544080, + "1297": 1784544080, + "1298": 1784544081, + "1300": 1784544081, + "1301": 1784544082, + "1303": 1784544082, + "1304": 1784544083, + "1306": 1784544084, + "1307": 1784544085, + "1309": 1784544085, + "1310": 1784544086, + "1312": 1784544086, + "1313": 1784544087, + "1315": 1784544088, + "1316": 1784544088, + "1318": 1784544089, + "1319": 1784544090, + "1321": 1784544091, + "1322": 1784544091, + "1324": 1784544092, + "1325": 1784544092, + "1327": 1784544093, + "1328": 1784544093, + "1330": 1784544094, + "1331": 1784544095, + "1332": 1784544095, + "1334": 1784544096, + "1336": 1784544097, + "1337": 1784544097, + "1339": 1784544098, + "1340": 1784544098, + "1342": 1784544099, + "1343": 1784544100, + "1345": 1784544101, + "1346": 1784544101, + "1348": 1784544102, + "1349": 1784544103, + "1351": 1784544104, + "1352": 1784544104, + "1354": 1784544105, + "1355": 1784544106, + "1357": 1784544106, + "1358": 1784544107, + "1360": 1784544108, + "1361": 1784544109, + "1363": 1784544109, + "1364": 1784544110, + "1366": 1784544111, + "1367": 1784544112, + "1369": 1784544112, + "1370": 1784544113, + "1372": 1784544113, + "1373": 1784544115, + "1375": 1784544115, + "1376": 1784544116, + "1378": 1784544193, + "1379": 1784544195, + "1381": 1784544195, + "1382": 1784544196, + "1384": 1784544197, + "1385": 1784544198, + "1387": 1784544198, + "1388": 1784544200, + "1390": 1784544200, + "1391": 1784544201, + "1393": 1784544201, + "1394": 1784544203, + "1396": 1784544203, + "1397": 1784544205, + "1399": 1784544206, + "1400": 1784544206, + "1402": 1784544207, + "1403": 1784544208, + "1405": 1784544209, + "1406": 1784544210, + "1408": 1784544211, + "1409": 1784544211, + "1411": 1784544212, + "1412": 1784544213, + "1413": 1784544213, + "1414": 1784544213, + "1671": 1784544219, + "1672": 1784544220, + "1673": 1784544221, + "1674": 1784544221, + "1677": 1784544231, + "1678": 1784544231, + "1683": 1784544234, + "1685": 1784544235, + "1686": 1784544235, + "1688": 1784544236, + "1689": 1784544237, + "1691": 1784544237, + "1692": 1784544238, + "1694": 1784544238, + "1695": 1784544239, + "1697": 1784544240, + "1698": 1784544240, + "1700": 1784544241, + "1701": 1784544242, + "1703": 1784544242, + "1704": 1784544243, + "1706": 1784544243, + "1707": 1784544244, + "1709": 1784544245, + "1710": 1784544245, + "1712": 1784544246, + "1713": 1784544246, + "1715": 1784544247, + "1716": 1784544248, + "1718": 1784544248, + "1719": 1784544249, + "1721": 1784544249, + "1722": 1784544250, + "1724": 1784544251, + "1725": 1784544251, + "1727": 1784544252, + "1728": 1784544253, + "1730": 1784544253, + "1731": 1784544254, + "1733": 1784544254, + "1734": 1784544255, + "1736": 1784544256, + "1737": 1784544256, + "1739": 1784544257, + "1740": 1784544257, + "1742": 1784544258, + "1743": 1784544259, + "1745": 1784544259, + "1746": 1784544260, + "1748": 1784544260, + "1749": 1784544261, + "1751": 1784544262, + "1752": 1784544262, + "1754": 1784544263, + "1755": 1784544264, + "1757": 1784544264, + "1758": 1784544265, + "1760": 1784544265, + "1761": 1784544266, + "1763": 1784544267, + "1764": 1784544268, + "1766": 1784544268, + "1767": 1784544269, + "1769": 1784544270, + "1770": 1784544271, + "1772": 1784544271, + "1773": 1784544272, + "1775": 1784544272, + "1776": 1784544274, + "1778": 1784544274, + "1779": 1784544275, + "1781": 1784544275, + "1782": 1784544276, + "1784": 1784544277, + "1785": 1784544280, + "1787": 1784544281, + "1788": 1784544281, + "1790": 1784544282, + "1791": 1784544283, + "1793": 1784544284, + "1794": 1784544284, + "1796": 1784544285, + "1797": 1784544286, + "1799": 1784544287, + "1800": 1784544288, + "1802": 1784544288, + "1803": 1784544289, + "1805": 1784544290, + "1806": 1784544291, + "1808": 1784544292, + "1809": 1784544293, + "1811": 1784544293, + "1812": 1784544294, + "1814": 1784544295, + "1815": 1784544296, + "1817": 1784544296, + "1818": 1784544297, + "1820": 1784544298, + "1821": 1784544299, + "1823": 1784544299, + "1824": 1784544301, + "1826": 1784544301, + "1827": 1784544302, + "1829": 1784544303, + "2086": 1784544309, + "2087": 1784544310, + "2088": 1784544311, + "2089": 1784544311 + }, + "logs": [ + { + "address": "0x3804b81f3ddfc34654211335e08e8837aa2d3f1a", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def" + ], + "data": "0x", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xc549f89cf1ca43eddecc64ac2208f4b283b1c483", + "topics": [ + "0xf57fedb261f4593784de9abb6653acfbaf45e74182818717c6e9b39c344a2a78", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a0000000000000000000000000000000000000000000000000000000000000024b12c9ede000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad14393900000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xaf68463e16cb5595a44214bea8d366ecf7cd3410269c50f92c104b50a7829daa" + ], + "data": "0x000000000000000000000000346b3df038fe9f8380071ec6514d5a83ad1439390000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a00000000000000000000000064f6cf454348f891e837eddc99be67ea98c64602", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e366c3ef306160a2770c6e55551c28436ede846c", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0x3804b81f3ddfc34654211335e08e8837aa2d3f1a", + "topics": [ + "0x6ad3188ba8f430fba0656cb0a7e839ab2020d5586ba11a1477d18f7092f8bece" + ], + "data": "0x000000000000000000000000669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x5", + "removed": false + }, + { + "address": "0x3804b81f3ddfc34654211335e08e8837aa2d3f1a", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000fdab499db8a58f8d04dc6ad2b05e72252b22def", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x6", + "removed": false + }, + { + "address": "0x0fdab499db8a58f8d04dc6ad2b05e72252b22def", + "topics": [ + "0xdf2ebeb5a7d7df0100c0274c7cee9570954d7bebeef37db55b27204a57f65602" + ], + "data": "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "blockHash": "0x8b7c62dff6f87086f9542000c9ef8508925f7007818c1db4aa16773ab3fe0f9d", + "blockNumber": "0x17", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x7e65aaf8388208422318474bb257e701569fea6c2c802d7f8669c15fd2cfbc33", + "transactionIndex": "0x0", + "logIndex": "0x7", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000006a5dfa752f6dd70a0193357ea820a45fe9a98e45deb5f07e1ec89f428a5d00cf15b30d9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3ba7b85de6186cf03116752d8a95964780a35412afa9028cc3532b5f4f433903", + "blockNumber": "0x18", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x5fe446a0623c5cc5255add1e0a95d49b6d598e427cc21773634febb15e02995a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000006a5dfa75d731e5b71666fe74144b20ab3cae10282340a35b4f77e8dbeb6f9471c2daaa7f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe83a58d7effcc8dbd95f20b52460327a0775735db08a0649d6d3b2353badf4f7", + "blockNumber": "0x1b", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x2c18d8fef8eaf172402e8969adacccd7b52b6ddea5bda80408410875d279c6de", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000006a5dfa7555573176e298f63541e023137716c8c1942c74cadf9492ce1510893140f9566300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x83de9ac84b0fba6c8d85e29d32c93e892345703dafcd8fffcd96dc618fd529c5", + "blockNumber": "0x1c", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x02b8d32291b364a463d36ec4548f0fe32225b40cefab4d71d827efd8ef248afd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000006a5dfa7554caeeb9f1bc8e01d3a557ed7b45f4dcdef77f9d006367cd6b25a27bbbe8c0e700000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdeac73d7360583c2adb9c218b4e1ade337d1df172db51d87ced9b26adbcf2bf2", + "blockNumber": "0x1d", + "blockTimestamp": "0x6a5dfa75", + "transactionHash": "0x1dd22873f95e7bc073756b06a4b30062142ba38eb10d9a6e02e4a0e33b2a59e9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x42e6cd681342ab8bfbb70139c38411de4a9f09e726412541deccc666a08775be", + "blockNumber": "0x20", + "blockTimestamp": "0x6a5dfa77", + "transactionHash": "0x1ec8c77b37e16914a0c981167937539b100add94a9651a0714c0592ebd647efb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xe366c3ef306160a2770c6e55551c28436ede846c", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x796de445c8edde19603688e962c800023225dc423f3b2873204131e1572a0c03", + "blockNumber": "0x21", + "blockTimestamp": "0x6a5dfa77", + "transactionHash": "0xddf13a71653aba945d6c562848a0260dca7fa864180e5c9b2f135fb4dabe49bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0x79ffce62643f446d70e1ea424a5dde7b02ca876fbf963d4c74d67d2db288967f", + "blockNumber": "0x1a3", + "blockTimestamp": "0x6a5dfa83", + "transactionHash": "0x5330a2e1c20a610f85f39230f5fdd7ed45e0e591bd74b31d9d5f72aafd6e7db8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x7ce9898681c7733e9f79d3d5dffcd17b2ef42dc248f60a0e8a7395edbbf54889", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5dfa83", + "transactionHash": "0xc6fb1bc304d4beca63aad6c780c72bc45fa578f627bd71ec65701eabbe4f2c3c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000401c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6000000000000000000000000914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "blockHash": "0x7ce9898681c7733e9f79d3d5dffcd17b2ef42dc248f60a0e8a7395edbbf54889", + "blockNumber": "0x1a4", + "blockTimestamp": "0x6a5dfa83", + "transactionHash": "0xc6fb1bc304d4beca63aad6c780c72bc45fa578f627bd71ec65701eabbe4f2c3c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xca79612641011c2b5e8ff393a42412e406dcbc1a37bf84d888eac13a26c73f84", + "blockNumber": "0x1a7", + "blockTimestamp": "0x6a5dfa8d", + "transactionHash": "0x5354f86e61733e3ca058e5a20c8528da6b3a9669dcc34bef15a795d254d9e97f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xe26400f971a68c1002a936dcb325c747f8cf05c7cef0cd1451ab5c4185a7e72f", + "blockNumber": "0x1a8", + "blockTimestamp": "0x6a5dfa8d", + "transactionHash": "0xeb5dc780be8cbbe0fc41cfb6ecce58c49a8e8a83edc689bd028e682d0c488492", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf3601c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xe46eeb6f438229700322f842c26ffce020c96764bfb67b04336ddbd6ad90441e", + "blockNumber": "0x1ad", + "blockTimestamp": "0x6a5dfa91", + "transactionHash": "0x461b70f9af5100455ea6bdceb13a562193ca2217a4bb71f225fbdfe80a63f59a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7b", + "blockHash": "0xe46eeb6f438229700322f842c26ffce020c96764bfb67b04336ddbd6ad90441e", + "blockNumber": "0x1ad", + "blockTimestamp": "0x6a5dfa91", + "transactionHash": "0x461b70f9af5100455ea6bdceb13a562193ca2217a4bb71f225fbdfe80a63f59a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7bfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442", + "blockHash": "0xf2a7e762c547b9d61e9a0f027bf3d4927b2065144e634bca83ff9c806dbf55ed", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5dfa92", + "transactionHash": "0xf10b7705a6b2adda1b4cf3be530bf6ab3f5d808f21c3c3e35e81c19fa22acaf8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf2a7e762c547b9d61e9a0f027bf3d4927b2065144e634bca83ff9c806dbf55ed", + "blockNumber": "0x1af", + "blockTimestamp": "0x6a5dfa92", + "transactionHash": "0xf10b7705a6b2adda1b4cf3be530bf6ab3f5d808f21c3c3e35e81c19fa22acaf8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442c28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe317", + "blockHash": "0x81a54c7ff042cebcaf3820e239aa5707e5b2fa94b308245d3bb30edac673c538", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5dfa92", + "transactionHash": "0x7708a93247ef5bdd55d5ff3346342340b62a6cefcfa6f44a9cf3806ee8827c9b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x81a54c7ff042cebcaf3820e239aa5707e5b2fa94b308245d3bb30edac673c538", + "blockNumber": "0x1b0", + "blockTimestamp": "0x6a5dfa92", + "transactionHash": "0x7708a93247ef5bdd55d5ff3346342340b62a6cefcfa6f44a9cf3806ee8827c9b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe3176c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd602", + "blockHash": "0x0efbe69119f53825f86bde0a60b5310717b77d56243c4d5391ce6a34b2c3a18f", + "blockNumber": "0x1b2", + "blockTimestamp": "0x6a5dfa93", + "transactionHash": "0x39917ac6aec0213081b829ac06488defb3fe938198d070b09af94b5aae03e1e9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0efbe69119f53825f86bde0a60b5310717b77d56243c4d5391ce6a34b2c3a18f", + "blockNumber": "0x1b2", + "blockTimestamp": "0x6a5dfa93", + "transactionHash": "0x39917ac6aec0213081b829ac06488defb3fe938198d070b09af94b5aae03e1e9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd6023080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a1248", + "blockHash": "0x7bf255b3286ffd694faee9f97f26ed294cb2c9461ce232e48ebf1e8998a0bb26", + "blockNumber": "0x1b3", + "blockTimestamp": "0x6a5dfa94", + "transactionHash": "0xa26f882f7cfa8d85dcc71aaf9a10b2629661438cba3d57ca2234f756e4f04f13", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7bf255b3286ffd694faee9f97f26ed294cb2c9461ce232e48ebf1e8998a0bb26", + "blockNumber": "0x1b3", + "blockTimestamp": "0x6a5dfa94", + "transactionHash": "0xa26f882f7cfa8d85dcc71aaf9a10b2629661438cba3d57ca2234f756e4f04f13", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a12487df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0ca", + "blockHash": "0x50df608e595cf091577380fd201e2df2f9e28b1fada80fd3c7cd0927d181e57e", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5dfa95", + "transactionHash": "0x9860a74452c87290ed2afe908388daa00cc4ce48041b3b6a425190c2749b7701", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x50df608e595cf091577380fd201e2df2f9e28b1fada80fd3c7cd0927d181e57e", + "blockNumber": "0x1b5", + "blockTimestamp": "0x6a5dfa95", + "transactionHash": "0x9860a74452c87290ed2afe908388daa00cc4ce48041b3b6a425190c2749b7701", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x7df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0caba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c0", + "blockHash": "0x92f92dd25efb0af7dd497f53803a4a79781a22f0f4450f1386527dc6248711bd", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5dfa95", + "transactionHash": "0x6ec8f2ab872bcb2cd7475ca968fe4313e0da993fc9f0f3659b39f070dcf20991", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x92f92dd25efb0af7dd497f53803a4a79781a22f0f4450f1386527dc6248711bd", + "blockNumber": "0x1b6", + "blockTimestamp": "0x6a5dfa95", + "transactionHash": "0x6ec8f2ab872bcb2cd7475ca968fe4313e0da993fc9f0f3659b39f070dcf20991", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c010f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583", + "blockHash": "0x3d5f5b1d68fdb9490a8222ac30690695de81d8e98443c58bb8a266c484285660", + "blockNumber": "0x1b8", + "blockTimestamp": "0x6a5dfa96", + "transactionHash": "0xcac7ce5362c6c0a625a9c94e16c71abb78a1a1aeb9d94559fce22a7e1124b93f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3d5f5b1d68fdb9490a8222ac30690695de81d8e98443c58bb8a266c484285660", + "blockNumber": "0x1b8", + "blockTimestamp": "0x6a5dfa96", + "transactionHash": "0xcac7ce5362c6c0a625a9c94e16c71abb78a1a1aeb9d94559fce22a7e1124b93f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x10f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583b502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb416735601", + "blockHash": "0x0aa5fd0a54ed521cf84912456030295838ff23b8aad085ba28b195022eaa7771", + "blockNumber": "0x1b9", + "blockTimestamp": "0x6a5dfa96", + "transactionHash": "0x0c3dd1e99d80521e012e8d14b6b08bc411125b4cc39cb0fd96039786e5dd5a88", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0aa5fd0a54ed521cf84912456030295838ff23b8aad085ba28b195022eaa7771", + "blockNumber": "0x1b9", + "blockTimestamp": "0x6a5dfa96", + "transactionHash": "0x0c3dd1e99d80521e012e8d14b6b08bc411125b4cc39cb0fd96039786e5dd5a88", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb4167356019a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6", + "blockHash": "0xe75bb52f57cc4489ba0964c650b99aa7781dfa2953124ef6747d6d3b2e551dca", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5dfa97", + "transactionHash": "0x03cab8b0a87667c7ec2c996b74932ad8bcafc0fec0c5563c45572e6811a6cf06", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe75bb52f57cc4489ba0964c650b99aa7781dfa2953124ef6747d6d3b2e551dca", + "blockNumber": "0x1bb", + "blockTimestamp": "0x6a5dfa97", + "transactionHash": "0x03cab8b0a87667c7ec2c996b74932ad8bcafc0fec0c5563c45572e6811a6cf06", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6d95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296", + "blockHash": "0x6187e007417ed7e2c0332ccf59ad672f0001823481a972fbbbd9d8372fa8834a", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5dfa97", + "transactionHash": "0x3abae8ab115b92120c203dabd1cad5baa772571b7fd364976d1a2eca14b5938f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6187e007417ed7e2c0332ccf59ad672f0001823481a972fbbbd9d8372fa8834a", + "blockNumber": "0x1bc", + "blockTimestamp": "0x6a5dfa97", + "transactionHash": "0x3abae8ab115b92120c203dabd1cad5baa772571b7fd364976d1a2eca14b5938f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1dea", + "blockHash": "0x0e2d5310a45f903f9b16fb31e290eafa524ac63f16d499c88779e9647aecba5d", + "blockNumber": "0x1be", + "blockTimestamp": "0x6a5dfa98", + "transactionHash": "0x15c6fedb789d478e99df753a4fe4599ce42de05af5350ccd1edba73de025daa1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0e2d5310a45f903f9b16fb31e290eafa524ac63f16d499c88779e9647aecba5d", + "blockNumber": "0x1be", + "blockTimestamp": "0x6a5dfa98", + "transactionHash": "0x15c6fedb789d478e99df753a4fe4599ce42de05af5350ccd1edba73de025daa1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1deaa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f171", + "blockHash": "0xf9adf7113099b07fcec161156e9615a6d60553c51c03391b86110c67160a13df", + "blockNumber": "0x1bf", + "blockTimestamp": "0x6a5dfa99", + "transactionHash": "0xff1e1da44a23955a7a8ea9a65a054ba21678d26f9ecfd115dceea2d997b7b72d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf9adf7113099b07fcec161156e9615a6d60553c51c03391b86110c67160a13df", + "blockNumber": "0x1bf", + "blockTimestamp": "0x6a5dfa99", + "transactionHash": "0xff1e1da44a23955a7a8ea9a65a054ba21678d26f9ecfd115dceea2d997b7b72d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f1714397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102", + "blockHash": "0x3397a76c5d3da5ca53a6071f9944f4b7bb1cf673f1015d6a47ed00f8c0cd5525", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfa9a", + "transactionHash": "0xe76220dad2d1d60c2ffba689d9d273b26dd196952f2bb0efdfa0085780cf484a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3397a76c5d3da5ca53a6071f9944f4b7bb1cf673f1015d6a47ed00f8c0cd5525", + "blockNumber": "0x1c1", + "blockTimestamp": "0x6a5dfa9a", + "transactionHash": "0xe76220dad2d1d60c2ffba689d9d273b26dd196952f2bb0efdfa0085780cf484a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102ff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a87", + "blockHash": "0x953ebf6ef68dc75f8cd18e79086da9d71f6b2581000d62b89a51d075a16b5016", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfa9a", + "transactionHash": "0x1dec09b6fb59c7d1c6668591f1786ef43a1c17e512097fc113b438bbad4fde30", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x953ebf6ef68dc75f8cd18e79086da9d71f6b2581000d62b89a51d075a16b5016", + "blockNumber": "0x1c2", + "blockTimestamp": "0x6a5dfa9a", + "transactionHash": "0x1dec09b6fb59c7d1c6668591f1786ef43a1c17e512097fc113b438bbad4fde30", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a8715797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d84", + "blockHash": "0x3f5d9e334d007dd2233cdf22102ad9747753a6c6e1cc9c02426d6f1a0a4ebc27", + "blockNumber": "0x1c4", + "blockTimestamp": "0x6a5dfa9b", + "transactionHash": "0xf5b009f1716dffe435f1d3a18335d0018249709469c9d3d80fd02c2aa7ebf63d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3f5d9e334d007dd2233cdf22102ad9747753a6c6e1cc9c02426d6f1a0a4ebc27", + "blockNumber": "0x1c4", + "blockTimestamp": "0x6a5dfa9b", + "transactionHash": "0xf5b009f1716dffe435f1d3a18335d0018249709469c9d3d80fd02c2aa7ebf63d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x15797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d848dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e", + "blockHash": "0xed93d1f53e1f0085e19763e4833cf35b734f71db63ec7f35a7d5c729ed6a97fd", + "blockNumber": "0x1c5", + "blockTimestamp": "0x6a5dfa9b", + "transactionHash": "0xb7146d8605404c38505c17d270bae7d8b6a7b758a2505ef0b457d604a776e526", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xed93d1f53e1f0085e19763e4833cf35b734f71db63ec7f35a7d5c729ed6a97fd", + "blockNumber": "0x1c5", + "blockTimestamp": "0x6a5dfa9b", + "transactionHash": "0xb7146d8605404c38505c17d270bae7d8b6a7b758a2505ef0b457d604a776e526", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5a", + "blockHash": "0x775a8b6df7c1b4fdd07b654bdd9f0efc72171c074d104dbdd450b29e72ab3aea", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfa9c", + "transactionHash": "0x970d0d7ca064dfe71b3b57fc611e9a7a555be2d8a74d20edff536839d3520d33", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x775a8b6df7c1b4fdd07b654bdd9f0efc72171c074d104dbdd450b29e72ab3aea", + "blockNumber": "0x1c7", + "blockTimestamp": "0x6a5dfa9c", + "transactionHash": "0x970d0d7ca064dfe71b3b57fc611e9a7a555be2d8a74d20edff536839d3520d33", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5ad4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2", + "blockHash": "0xc71ec07e84d0f4a32e1d5eba3a49da53c6214acda3ea49595a5007af1355d5c6", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfa9c", + "transactionHash": "0x5d8a30e20568511e607e632a13164fcc0824226660c1852011dd0cb619a346e5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc71ec07e84d0f4a32e1d5eba3a49da53c6214acda3ea49595a5007af1355d5c6", + "blockNumber": "0x1c8", + "blockTimestamp": "0x6a5dfa9c", + "transactionHash": "0x5d8a30e20568511e607e632a13164fcc0824226660c1852011dd0cb619a346e5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2b262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203", + "blockHash": "0x6326cc1a247ee848a6e5348066744210e5a4c30ec4cc87a8a597b95286037bdb", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfa9d", + "transactionHash": "0xf6f227baea68a9daf658c7fc12517d4ce458ab9b6e6ba857ed7ef63b930b70a9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6326cc1a247ee848a6e5348066744210e5a4c30ec4cc87a8a597b95286037bdb", + "blockNumber": "0x1ca", + "blockTimestamp": "0x6a5dfa9d", + "transactionHash": "0xf6f227baea68a9daf658c7fc12517d4ce458ab9b6e6ba857ed7ef63b930b70a9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203dbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd87058277565", + "blockHash": "0x6bd48207bff36f9fd8aeaed6d971845764455f0efb6d8080cdfe6f5da936dab6", + "blockNumber": "0x1cb", + "blockTimestamp": "0x6a5dfa9d", + "transactionHash": "0xfce6a12ebb8ed72c8a89148827546980f0f9a53344b6dfec41ff1fab73c9893e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6bd48207bff36f9fd8aeaed6d971845764455f0efb6d8080cdfe6f5da936dab6", + "blockNumber": "0x1cb", + "blockTimestamp": "0x6a5dfa9d", + "transactionHash": "0xfce6a12ebb8ed72c8a89148827546980f0f9a53344b6dfec41ff1fab73c9893e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd870582775656564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a", + "blockHash": "0xa72f9747733a17c91c2f4953857c915dfe609f6b28329fbb5c11d20502980fb8", + "blockNumber": "0x1cd", + "blockTimestamp": "0x6a5dfa9e", + "transactionHash": "0x242abd0d1fe97ba2a95f0e082b2b18b460a2912638ebb1e0192f442fe38d9a2f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa72f9747733a17c91c2f4953857c915dfe609f6b28329fbb5c11d20502980fb8", + "blockNumber": "0x1cd", + "blockTimestamp": "0x6a5dfa9e", + "transactionHash": "0x242abd0d1fe97ba2a95f0e082b2b18b460a2912638ebb1e0192f442fe38d9a2f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cab", + "blockHash": "0xf9ab2973f51bf6049692e1e9f6eb48ce159c26ef7de5eb8829342021dd07d2bd", + "blockNumber": "0x1ce", + "blockTimestamp": "0x6a5dfa9f", + "transactionHash": "0xd55ac5f3cdcd8bede3ed4cb92e44033a7dac8a353afce93d0a2806c52806ae41", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf9ab2973f51bf6049692e1e9f6eb48ce159c26ef7de5eb8829342021dd07d2bd", + "blockNumber": "0x1ce", + "blockTimestamp": "0x6a5dfa9f", + "transactionHash": "0xd55ac5f3cdcd8bede3ed4cb92e44033a7dac8a353afce93d0a2806c52806ae41", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cabe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730", + "blockHash": "0x9522e3e1b333df0ce50551228a1e1ee073d4c6894c4ec87159229a26eece1542", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfaa0", + "transactionHash": "0xbcc0d7d2683aa11e43cd31c605915134eb987e5f52a3513d964b4bca8d399f44", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9522e3e1b333df0ce50551228a1e1ee073d4c6894c4ec87159229a26eece1542", + "blockNumber": "0x1d0", + "blockTimestamp": "0x6a5dfaa0", + "transactionHash": "0xbcc0d7d2683aa11e43cd31c605915134eb987e5f52a3513d964b4bca8d399f44", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3", + "blockHash": "0x0431cf85d75cfe3afcbbd522355fe7d4fbe789238fa1730ef51056235723f55e", + "blockNumber": "0x1d1", + "blockTimestamp": "0x6a5dfaa0", + "transactionHash": "0x867e337d68cb79f500d7ef6c9176af9d7d7761f5a6dc2d80cfeba113131ab5cf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0431cf85d75cfe3afcbbd522355fe7d4fbe789238fa1730ef51056235723f55e", + "blockNumber": "0x1d1", + "blockTimestamp": "0x6a5dfaa0", + "transactionHash": "0x867e337d68cb79f500d7ef6c9176af9d7d7761f5a6dc2d80cfeba113131ab5cf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3cb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882", + "blockHash": "0x210bc6cfa7705fe2bf2207095b83d5e5564692c806abcd6202de202930c4dba4", + "blockNumber": "0x1d3", + "blockTimestamp": "0x6a5dfaa1", + "transactionHash": "0x61f2bc5d327c1386181f61d409aa37f257cc3c8a0fea053a8f403df4f5337ca2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x210bc6cfa7705fe2bf2207095b83d5e5564692c806abcd6202de202930c4dba4", + "blockNumber": "0x1d3", + "blockTimestamp": "0x6a5dfaa1", + "transactionHash": "0x61f2bc5d327c1386181f61d409aa37f257cc3c8a0fea053a8f403df4f5337ca2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882c962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c", + "blockHash": "0x5954f23691ee26b172909e6bfacebfc015af1203b339c225a803ba87d22e5aaa", + "blockNumber": "0x1d4", + "blockTimestamp": "0x6a5dfaa1", + "transactionHash": "0xc30b7880c06567c29208819c1742ff93a055ca5c3c7c08e5a2110afe74aa8a91", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5954f23691ee26b172909e6bfacebfc015af1203b339c225a803ba87d22e5aaa", + "blockNumber": "0x1d4", + "blockTimestamp": "0x6a5dfaa1", + "transactionHash": "0xc30b7880c06567c29208819c1742ff93a055ca5c3c7c08e5a2110afe74aa8a91", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693", + "blockHash": "0xdb4dd0eb7739628deff34f4ba21d7410e922e6aa55a4bf0407988b1a979d0916", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfaa2", + "transactionHash": "0xaace0f2479867275836336a17b56f7881a8d855c50984976196fb1a75795603f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdb4dd0eb7739628deff34f4ba21d7410e922e6aa55a4bf0407988b1a979d0916", + "blockNumber": "0x1d6", + "blockTimestamp": "0x6a5dfaa2", + "transactionHash": "0xaace0f2479867275836336a17b56f7881a8d855c50984976196fb1a75795603f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693a0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac3", + "blockHash": "0xd1fe1ec3fecd1b9c7e8b11000c66029e0e073d1d417aabd28e5fb6078acc9515", + "blockNumber": "0x1d7", + "blockTimestamp": "0x6a5dfaa2", + "transactionHash": "0xe32d6bc445d2c27ddbd26eef16b9c60421eb1a1a01a88f78a1f74078ea3f65b0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd1fe1ec3fecd1b9c7e8b11000c66029e0e073d1d417aabd28e5fb6078acc9515", + "blockNumber": "0x1d7", + "blockTimestamp": "0x6a5dfaa2", + "transactionHash": "0xe32d6bc445d2c27ddbd26eef16b9c60421eb1a1a01a88f78a1f74078ea3f65b0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac31138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37f", + "blockHash": "0xf8e096772b8df7372cf55044633456f89edfe2f1d10fcb04252e6e146c115886", + "blockNumber": "0x1d9", + "blockTimestamp": "0x6a5dfaa3", + "transactionHash": "0x4372c636d6734f59020b7f8c76df056657277c99c3c37c5a5a7dfd6c7ebc57c9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf8e096772b8df7372cf55044633456f89edfe2f1d10fcb04252e6e146c115886", + "blockNumber": "0x1d9", + "blockTimestamp": "0x6a5dfaa3", + "transactionHash": "0x4372c636d6734f59020b7f8c76df056657277c99c3c37c5a5a7dfd6c7ebc57c9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37fdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653ab", + "blockHash": "0x1404da72bfd2329b1c2599aaad050490116539676a011d234e3bf7831c0fdc2e", + "blockNumber": "0x1da", + "blockTimestamp": "0x6a5dfaa4", + "transactionHash": "0x3f8c52bb5f206be54f5dee24b35811ce7264f1ad01ed65f154735eb12c36c312", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1404da72bfd2329b1c2599aaad050490116539676a011d234e3bf7831c0fdc2e", + "blockNumber": "0x1da", + "blockTimestamp": "0x6a5dfaa4", + "transactionHash": "0x3f8c52bb5f206be54f5dee24b35811ce7264f1ad01ed65f154735eb12c36c312", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653abc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba633", + "blockHash": "0x3f8b95c739b460452cf175c2bc483b3866b379db2a9cdf1a640eb7beec4d495a", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfaa4", + "transactionHash": "0x3d4a01f464d8c3107e13a339326463a52af6daa3430157ae5d849afa259313e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3f8b95c739b460452cf175c2bc483b3866b379db2a9cdf1a640eb7beec4d495a", + "blockNumber": "0x1dc", + "blockTimestamp": "0x6a5dfaa4", + "transactionHash": "0x3d4a01f464d8c3107e13a339326463a52af6daa3430157ae5d849afa259313e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba6338ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d1790", + "blockHash": "0x6659855106ee5d2a73a49d5871a02336eaaadc024e4dda5fd15a8386b1432c8d", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfaa5", + "transactionHash": "0x764bd58fe51d7794a7047099036c26ee62b56faf1e0a5f2a701e936f06aa7d2e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6659855106ee5d2a73a49d5871a02336eaaadc024e4dda5fd15a8386b1432c8d", + "blockNumber": "0x1dd", + "blockTimestamp": "0x6a5dfaa5", + "transactionHash": "0x764bd58fe51d7794a7047099036c26ee62b56faf1e0a5f2a701e936f06aa7d2e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d179002686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca", + "blockHash": "0x3dbf6f6aa5d6117ca99181e1e894d43400285759bea3b5404a571ab4fd15ced8", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfaa6", + "transactionHash": "0x37c98f3bb5d6a802f8ac1f08730127fba77af25adc870947fb80b16af33d3e2d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3dbf6f6aa5d6117ca99181e1e894d43400285759bea3b5404a571ab4fd15ced8", + "blockNumber": "0x1df", + "blockTimestamp": "0x6a5dfaa6", + "transactionHash": "0x37c98f3bb5d6a802f8ac1f08730127fba77af25adc870947fb80b16af33d3e2d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x02686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f36331", + "blockHash": "0x10e81f4fcc600f822618b6e141ed8e85f248e8eb04c2e18cfdcc64cd74d4e0cf", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfaa6", + "transactionHash": "0x51c8e20bd50858b9e7151e1969e9c97d644fafb0a819dc3a3f25c9e8d09345d2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x10e81f4fcc600f822618b6e141ed8e85f248e8eb04c2e18cfdcc64cd74d4e0cf", + "blockNumber": "0x1e0", + "blockTimestamp": "0x6a5dfaa6", + "transactionHash": "0x51c8e20bd50858b9e7151e1969e9c97d644fafb0a819dc3a3f25c9e8d09345d2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f3633137a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f1", + "blockHash": "0xe6a77ef41c6047a3d4d041f48fe762be6a3838978179a0398ae872aea9f4e565", + "blockNumber": "0x1e2", + "blockTimestamp": "0x6a5dfaa7", + "transactionHash": "0x9561a6ecfd8bd65c238002e161408e57c9f75275e0e051d14d014ed0c32659f9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe6a77ef41c6047a3d4d041f48fe762be6a3838978179a0398ae872aea9f4e565", + "blockNumber": "0x1e2", + "blockTimestamp": "0x6a5dfaa7", + "transactionHash": "0x9561a6ecfd8bd65c238002e161408e57c9f75275e0e051d14d014ed0c32659f9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x37a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f11fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7", + "blockHash": "0x52c40e8210dde5c181465cf8eb6555e5ef897467d89aacfc1399b8288d3dd7f3", + "blockNumber": "0x1e3", + "blockTimestamp": "0x6a5dfaa7", + "transactionHash": "0xed5950de6e811ecb9ed8ef9ca8933a184f28696c7a84f6ef3cefad98b8b8dd67", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x52c40e8210dde5c181465cf8eb6555e5ef897467d89aacfc1399b8288d3dd7f3", + "blockNumber": "0x1e3", + "blockTimestamp": "0x6a5dfaa7", + "transactionHash": "0xed5950de6e811ecb9ed8ef9ca8933a184f28696c7a84f6ef3cefad98b8b8dd67", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7cbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689", + "blockHash": "0xc21aaf64e0423f59600ecd99bbe057c75feed82ee7ca118a639815485dda71e6", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfaa8", + "transactionHash": "0x71755c4a24dae141103066fc1c5e1f44a61c06724a74701180a8817af73859b1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc21aaf64e0423f59600ecd99bbe057c75feed82ee7ca118a639815485dda71e6", + "blockNumber": "0x1e5", + "blockTimestamp": "0x6a5dfaa8", + "transactionHash": "0x71755c4a24dae141103066fc1c5e1f44a61c06724a74701180a8817af73859b1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f6896125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f183", + "blockHash": "0x3ec67e6d58b51a7f7707d19cac866d6d269169eaff50d81db5a784696f03cbec", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfaa8", + "transactionHash": "0x6d0c7443abb23f244337d6f784798eb5b033b337159ce40268e3625d675bd6ed", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3ec67e6d58b51a7f7707d19cac866d6d269169eaff50d81db5a784696f03cbec", + "blockNumber": "0x1e6", + "blockTimestamp": "0x6a5dfaa8", + "transactionHash": "0x6d0c7443abb23f244337d6f784798eb5b033b337159ce40268e3625d675bd6ed", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f18303e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d", + "blockHash": "0x644dc2ea926fd62138d3c3229e951c65c270f1200386c110581cc5c9d02c1637", + "blockNumber": "0x1e8", + "blockTimestamp": "0x6a5dfaa9", + "transactionHash": "0x7d43f035cb973b1200aad7000fc1a282fe99a1d184ba667f529676fd98f7eaf3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x644dc2ea926fd62138d3c3229e951c65c270f1200386c110581cc5c9d02c1637", + "blockNumber": "0x1e8", + "blockTimestamp": "0x6a5dfaa9", + "transactionHash": "0x7d43f035cb973b1200aad7000fc1a282fe99a1d184ba667f529676fd98f7eaf3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x03e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a", + "blockHash": "0x3bd599586448b2f68e273338cd012cfe388a2ef44b091d39fd0e959a9b7a186a", + "blockNumber": "0x1e9", + "blockTimestamp": "0x6a5dfaaa", + "transactionHash": "0xf2c2d71bf0ac0e072ad0a3a946ddbf457e9c47c664abaf8bfbdd503591cc9b60", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3bd599586448b2f68e273338cd012cfe388a2ef44b091d39fd0e959a9b7a186a", + "blockNumber": "0x1e9", + "blockTimestamp": "0x6a5dfaaa", + "transactionHash": "0xf2c2d71bf0ac0e072ad0a3a946ddbf457e9c47c664abaf8bfbdd503591cc9b60", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f88", + "blockHash": "0x9a5da2da1b51110879692efdad0fe0deaeed10c2007d9bfb31b6619793c745fc", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfaab", + "transactionHash": "0x9c106a79d85773e6ff1f90ac8c34dffcd9edc8432d0e7a9e8c6c7493c76e9c3d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9a5da2da1b51110879692efdad0fe0deaeed10c2007d9bfb31b6619793c745fc", + "blockNumber": "0x1eb", + "blockTimestamp": "0x6a5dfaab", + "transactionHash": "0x9c106a79d85773e6ff1f90ac8c34dffcd9edc8432d0e7a9e8c6c7493c76e9c3d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f885a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd8925", + "blockHash": "0xe5ecff4c21f7bb817c563ffd60d65faeb10bf2177682c721329064fb2edd3fe9", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfaab", + "transactionHash": "0x8a254f9f57afa178330d559563f919f50aa9a82768b7ece951079125abf1ca21", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe5ecff4c21f7bb817c563ffd60d65faeb10bf2177682c721329064fb2edd3fe9", + "blockNumber": "0x1ec", + "blockTimestamp": "0x6a5dfaab", + "transactionHash": "0x8a254f9f57afa178330d559563f919f50aa9a82768b7ece951079125abf1ca21", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x5a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd89254b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd77", + "blockHash": "0x31164d5b69751930376e20d897483af6b3fe2b64abfbc5dc2449f34a251dcfb4", + "blockNumber": "0x1ee", + "blockTimestamp": "0x6a5dfaac", + "transactionHash": "0xdec781ace352b02b866c1f6d3fa4cf7ff6c996c4fcfd959f20d89d142ed41708", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x31164d5b69751930376e20d897483af6b3fe2b64abfbc5dc2449f34a251dcfb4", + "blockNumber": "0x1ee", + "blockTimestamp": "0x6a5dfaac", + "transactionHash": "0xdec781ace352b02b866c1f6d3fa4cf7ff6c996c4fcfd959f20d89d142ed41708", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd7771c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98b", + "blockHash": "0x3d2eb2b3391b82db91224537791b0e76152090c8e0ea018bf7ad7d3bd4e9b5c1", + "blockNumber": "0x1ef", + "blockTimestamp": "0x6a5dfaac", + "transactionHash": "0x3123b707b666914155f246f568ad694de703a43b72a191b3dc61fbd51ac96a6a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3d2eb2b3391b82db91224537791b0e76152090c8e0ea018bf7ad7d3bd4e9b5c1", + "blockNumber": "0x1ef", + "blockTimestamp": "0x6a5dfaac", + "transactionHash": "0x3123b707b666914155f246f568ad694de703a43b72a191b3dc61fbd51ac96a6a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x71c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98bcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402", + "blockHash": "0xc1e2f505ba3c6e3902815797e4728ac0c4992911d59ea5fd3e6837bda7b531e2", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dfaad", + "transactionHash": "0xb22ee1c532961ac7a2bf7ed4a91b34ee690d81e9853e673ce71bd93bc8b5510a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc1e2f505ba3c6e3902815797e4728ac0c4992911d59ea5fd3e6837bda7b531e2", + "blockNumber": "0x1f1", + "blockTimestamp": "0x6a5dfaad", + "transactionHash": "0xb22ee1c532961ac7a2bf7ed4a91b34ee690d81e9853e673ce71bd93bc8b5510a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402d2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b", + "blockHash": "0x3b7014056c43b647bb9b91c8e8fcf23c8dd97fcccf31b57737720a3677b56759", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dfaad", + "transactionHash": "0x39732d411132f3f76b0a186fa529b025d489bfab9743d86f3fa3361e396e2e1c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3b7014056c43b647bb9b91c8e8fcf23c8dd97fcccf31b57737720a3677b56759", + "blockNumber": "0x1f2", + "blockTimestamp": "0x6a5dfaad", + "transactionHash": "0x39732d411132f3f76b0a186fa529b025d489bfab9743d86f3fa3361e396e2e1c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xb8a66aaa3295a6b547ee1d4c403fb03994c995885a714f21fb82f4dd4635738d", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dfaae", + "transactionHash": "0x4395df5ae270d97ec8738fcfa277fd262b0930d3e7f5402abbde0f8d375862bc", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb8a66aaa3295a6b547ee1d4c403fb03994c995885a714f21fb82f4dd4635738d", + "blockNumber": "0x1f4", + "blockTimestamp": "0x6a5dfaae", + "transactionHash": "0x4395df5ae270d97ec8738fcfa277fd262b0930d3e7f5402abbde0f8d375862bc", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x0000000000000000000000006b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7" + ], + "data": "0x", + "blockHash": "0xc51d63ca26b20b37dcef31593b6f0f45cf5d99070886c6a294bf765d2d6eb42e", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dfaaf", + "transactionHash": "0xfb8691dce03fc5c67167d36cf1fa5408ec05e2d71ba3e26193b7d8a3c5f0fe59", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000024e86000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc51d63ca26b20b37dcef31593b6f0f45cf5d99070886c6a294bf765d2d6eb42e", + "blockNumber": "0x1f5", + "blockTimestamp": "0x6a5dfaaf", + "transactionHash": "0xfb8691dce03fc5c67167d36cf1fa5408ec05e2d71ba3e26193b7d8a3c5f0fe59", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x96d30e859fad055ab8e02b8255226b0b2290dc4e2c5c97beafa1aa220e8aebf501c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x15c7f408d95aef95df04f20a7edab8c43cc782b8b76083b9006eac211a7d7454", + "blockNumber": "0x1f6", + "blockTimestamp": "0x6a5dfaaf", + "transactionHash": "0x2fb6e3cd7e2413fe4cc4ab23335cdd2c9f57ca212872f65e759a150da94b2638", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0x4cba036a41b6fc90be13313248640e6df05ab6aaf9dddddd7beeadddcef49fff0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x033a1add7f953dfe53e74eefe49df71d465638dd7e7ed1a12b8a46f79dfa0a2f", + "blockNumber": "0x1f8", + "blockTimestamp": "0x6a5dfab0", + "transactionHash": "0xeb302bdb35e3e95ef3aa00bf3b4a927e66da4762872d43915d5faeb0c4c1867e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881", + "0x96d30e859fad055ab8e02b8255226b0b2290dc4e2c5c97beafa1aa220e8aebf5", + "0x4cba036a41b6fc90be13313248640e6df05ab6aaf9dddddd7beeadddcef49fff" + ], + "data": "0x285ce2dd1ba034ac47404b68c60cf65c5ef6334a5776aed0d82fd38b961906c8", + "blockHash": "0x033a1add7f953dfe53e74eefe49df71d465638dd7e7ed1a12b8a46f79dfa0a2f", + "blockNumber": "0x1f8", + "blockTimestamp": "0x6a5dfab0", + "transactionHash": "0xeb302bdb35e3e95ef3aa00bf3b4a927e66da4762872d43915d5faeb0c4c1867e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x38978abd4b33d8a6af88e257eb6838ab7582dc36f7894a1400c4449250efc3524d112390a2069e0781a6a30b20a075164136024b1e31e5384fbb74010de55e10", + "blockHash": "0xa67400a538f5243fc05c076a2624ae22fb843c7ed469e84b0037002c49e16434", + "blockNumber": "0x1fa", + "blockTimestamp": "0x6a5dfab1", + "transactionHash": "0x8c294ebf29bc9af86cfdf6aa2961d46c0534d2adba27106ba50f37362c96e559", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa67400a538f5243fc05c076a2624ae22fb843c7ed469e84b0037002c49e16434", + "blockNumber": "0x1fa", + "blockTimestamp": "0x6a5dfab1", + "transactionHash": "0x8c294ebf29bc9af86cfdf6aa2961d46c0534d2adba27106ba50f37362c96e559", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x4d112390a2069e0781a6a30b20a075164136024b1e31e5384fbb74010de55e1002686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca", + "blockHash": "0xec52b06b2d67744e42daad3d2a034b1481603428b91c92cb8d154f2c65249f37", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dfab2", + "transactionHash": "0x6bb86713cd7702859e80d5c311ae8acaf8d9619e83b1519b0e5826e0100fcf55", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xec52b06b2d67744e42daad3d2a034b1481603428b91c92cb8d154f2c65249f37", + "blockNumber": "0x1fb", + "blockTimestamp": "0x6a5dfab2", + "transactionHash": "0x6bb86713cd7702859e80d5c311ae8acaf8d9619e83b1519b0e5826e0100fcf55", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x15d19e383483c6fb35555e5eac7feb07bbeaab7137596c51fd4f917b8a8f17739779995ff6b324458cce7ed27c799c5a645dd32096a171ca69ae11c54a8f6639", + "blockHash": "0x0631af0f0e36a1219e0417fbc316d1b37b30e3bb3011fc36463bb6aa8f326c1a", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dfab2", + "transactionHash": "0x8d6717e3530d64c623bbf34f909a97b0cc15d52a63d18bbd6c3ecafb6a2e7a0d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0631af0f0e36a1219e0417fbc316d1b37b30e3bb3011fc36463bb6aa8f326c1a", + "blockNumber": "0x1fd", + "blockTimestamp": "0x6a5dfab2", + "transactionHash": "0x8d6717e3530d64c623bbf34f909a97b0cc15d52a63d18bbd6c3ecafb6a2e7a0d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x9779995ff6b324458cce7ed27c799c5a645dd32096a171ca69ae11c54a8f663937a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f1", + "blockHash": "0xce54ff74d3e840447ae84afef75003ab114db95dc6b1878fb198340e02dcecae", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dfab3", + "transactionHash": "0xb6602515652677561860ea8541ad77d5f536dcb94ae157518a77ca3469622461", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xce54ff74d3e840447ae84afef75003ab114db95dc6b1878fb198340e02dcecae", + "blockNumber": "0x1fe", + "blockTimestamp": "0x6a5dfab3", + "transactionHash": "0xb6602515652677561860ea8541ad77d5f536dcb94ae157518a77ca3469622461", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x74335c36a842011f59ff7300b914652b8cf501a6edd083ead208d30172c41cfe9f161eeb7f79083bf5289f0892e0f8a3d11fc73275a1ac8e26df840831bf2933", + "blockHash": "0x1ed5f0204d4c020dc26fbc3fe0fde78667bd72e2d0c85b156ad3fe6fdb6bc1b1", + "blockNumber": "0x200", + "blockTimestamp": "0x6a5dfab4", + "transactionHash": "0x5f844c88c4bcbf772e99ec7b3bdd28dfecdf18cae55aa10d184acf378dcf692e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1ed5f0204d4c020dc26fbc3fe0fde78667bd72e2d0c85b156ad3fe6fdb6bc1b1", + "blockNumber": "0x200", + "blockTimestamp": "0x6a5dfab4", + "transactionHash": "0x5f844c88c4bcbf772e99ec7b3bdd28dfecdf18cae55aa10d184acf378dcf692e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x9f161eeb7f79083bf5289f0892e0f8a3d11fc73275a1ac8e26df840831bf2933cbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689", + "blockHash": "0x8bf0f3e31e7a4c3af534f1d1ecc7ac669e42cdf97fee32d4a27d59abd6c6b890", + "blockNumber": "0x201", + "blockTimestamp": "0x6a5dfab5", + "transactionHash": "0x13a9582551eaed57cb4d8e973b3253a021a7555bd61a4a27222126b0c90b9bdd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8bf0f3e31e7a4c3af534f1d1ecc7ac669e42cdf97fee32d4a27d59abd6c6b890", + "blockNumber": "0x201", + "blockTimestamp": "0x6a5dfab5", + "transactionHash": "0x13a9582551eaed57cb4d8e973b3253a021a7555bd61a4a27222126b0c90b9bdd", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x13a2e845281925d335c1a45d4d7924dd5e2dc27b1c6a17b5851a9dda2da6ff9c07e0b23aae4d00119dc62cbb2beace8e1d8bc5619bc400ff9723792df5c57d2e", + "blockHash": "0x77eec05dac7c50da0a45937217fd3a54ae0e094260ae7f2280f04b8fa3d27e4f", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dfab5", + "transactionHash": "0x34b5a46b3d151c7b11d7f0812925cd70519c43d6d89021015c96134121e4d7d4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x77eec05dac7c50da0a45937217fd3a54ae0e094260ae7f2280f04b8fa3d27e4f", + "blockNumber": "0x203", + "blockTimestamp": "0x6a5dfab5", + "transactionHash": "0x34b5a46b3d151c7b11d7f0812925cd70519c43d6d89021015c96134121e4d7d4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x07e0b23aae4d00119dc62cbb2beace8e1d8bc5619bc400ff9723792df5c57d2e03e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d", + "blockHash": "0xf8baf555c7488303ff6440cbbc8b2b4f840152d97ed3ce29c61f22a646db8bcf", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dfab6", + "transactionHash": "0x3ab429f4025399736d5aea727b8d3a33dae3b548c24c418f2592b9b51705fa25", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf8baf555c7488303ff6440cbbc8b2b4f840152d97ed3ce29c61f22a646db8bcf", + "blockNumber": "0x204", + "blockTimestamp": "0x6a5dfab6", + "transactionHash": "0x3ab429f4025399736d5aea727b8d3a33dae3b548c24c418f2592b9b51705fa25", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0xbb75bd09281138949eaf07c5749e1c81dc65d892967a620bf546d400f7d31b0022bb7c8e748c818e07d70fa3ed53961eb2b6521c5a446911c3ea87eb16ece59c", + "blockHash": "0x4d4a16190b8a247a871b4bf6c3c35df28aa7458b7919cd3ac7871075996fcfe2", + "blockNumber": "0x206", + "blockTimestamp": "0x6a5dfab7", + "transactionHash": "0x1672156f9c1fff12c07129a5d1a627bde3718f48e0b0760591e42d683d5fe9aa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4d4a16190b8a247a871b4bf6c3c35df28aa7458b7919cd3ac7871075996fcfe2", + "blockNumber": "0x206", + "blockTimestamp": "0x6a5dfab7", + "transactionHash": "0x1672156f9c1fff12c07129a5d1a627bde3718f48e0b0760591e42d683d5fe9aa", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x22bb7c8e748c818e07d70fa3ed53961eb2b6521c5a446911c3ea87eb16ece59c2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f88", + "blockHash": "0x440ad53c0f22528415f0c085828b05d1293242b9bd0babeb51fc1a7db75d70a4", + "blockNumber": "0x207", + "blockTimestamp": "0x6a5dfab7", + "transactionHash": "0x127a5eb8c971871b13c534121996d40b0811dff504fe19941c32b8b249867a86", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x440ad53c0f22528415f0c085828b05d1293242b9bd0babeb51fc1a7db75d70a4", + "blockNumber": "0x207", + "blockTimestamp": "0x6a5dfab7", + "transactionHash": "0x127a5eb8c971871b13c534121996d40b0811dff504fe19941c32b8b249867a86", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0xc0d1cfef1b6f35f3025697d3bc1969b51a42a785813902736e628dc43d1ab2e9b5a4b9588efc763331f8dd4fd5b57e26124fa91e872b7afbcfcdec9c28d7bddb", + "blockHash": "0x8ac7c3a1d90cefe12b13becc34410a90ba36cf9917d67fdfc110f3bfeb86f64c", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dfab8", + "transactionHash": "0x0e89b7918aadba657f83750a58b9e4b6e1364b288d3f1242bbcf91734e91f91e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8ac7c3a1d90cefe12b13becc34410a90ba36cf9917d67fdfc110f3bfeb86f64c", + "blockNumber": "0x209", + "blockTimestamp": "0x6a5dfab8", + "transactionHash": "0x0e89b7918aadba657f83750a58b9e4b6e1364b288d3f1242bbcf91734e91f91e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0xb5a4b9588efc763331f8dd4fd5b57e26124fa91e872b7afbcfcdec9c28d7bddb4b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd77", + "blockHash": "0xb95f25832ae9aa3023504866805acd41303ac986af5f37c60dc6ed92c9365233", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dfab9", + "transactionHash": "0x04765b68fdc4bb0fe0cf8aa8fcffad8b8aaccd9fb31b254d026d5efc7c5343bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb95f25832ae9aa3023504866805acd41303ac986af5f37c60dc6ed92c9365233", + "blockNumber": "0x20a", + "blockTimestamp": "0x6a5dfab9", + "transactionHash": "0x04765b68fdc4bb0fe0cf8aa8fcffad8b8aaccd9fb31b254d026d5efc7c5343bb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0xec2022f5db4466faa846caf42966d0811e7e1fd4cdbfa15886344e5e23767892808fe019d787760037e7c5502cfe11b83ab0324f218c6bc2744d81ffe6c713cf", + "blockHash": "0x739e254251244b00e081f18e718325cb096304983d6ee896cbf6a9ea8609e80e", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dfab9", + "transactionHash": "0x29b22adbae7ad4d0e77d0463d9c32421106004a5055d658c05f4dba6d8403031", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x739e254251244b00e081f18e718325cb096304983d6ee896cbf6a9ea8609e80e", + "blockNumber": "0x20c", + "blockTimestamp": "0x6a5dfab9", + "transactionHash": "0x29b22adbae7ad4d0e77d0463d9c32421106004a5055d658c05f4dba6d8403031", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x808fe019d787760037e7c5502cfe11b83ab0324f218c6bc2744d81ffe6c713cfcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402", + "blockHash": "0x1dc8344456587f6a8dd954330ee5545a6af9095257780fc1df05c82b08b255f7", + "blockNumber": "0x20d", + "blockTimestamp": "0x6a5dfaba", + "transactionHash": "0x4a3f8967986e38250e11c6bff7625f3c0919f73d38fa6e975f7017ae1d8cfc46", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1dc8344456587f6a8dd954330ee5545a6af9095257780fc1df05c82b08b255f7", + "blockNumber": "0x20d", + "blockTimestamp": "0x6a5dfaba", + "transactionHash": "0x4a3f8967986e38250e11c6bff7625f3c0919f73d38fa6e975f7017ae1d8cfc46", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x6bec3163a831ba29ede7eefffb363756cc6046f956503829a87cca6a4dbfd774025a36c32ce32ba22736562af4b1a226eaef2541bbae150892c890c1f7728d66", + "blockHash": "0x1f2baaef7b2bf90ae31f5ebf6b6f1bc092404c6e3321f2a52b5565cb11be5875", + "blockNumber": "0x20f", + "blockTimestamp": "0x6a5dfabb", + "transactionHash": "0x04ccb5a19695d9541f1dfb2268485087f6f3ad36d16f896dbddeaad2cafa53c6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1f2baaef7b2bf90ae31f5ebf6b6f1bc092404c6e3321f2a52b5565cb11be5875", + "blockNumber": "0x20f", + "blockTimestamp": "0x6a5dfabb", + "transactionHash": "0x04ccb5a19695d9541f1dfb2268485087f6f3ad36d16f896dbddeaad2cafa53c6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881" + ], + "data": "0x025a36c32ce32ba22736562af4b1a226eaef2541bbae150892c890c1f7728d6601c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x7fde866c72dfa26e56e43de9e6a330e34c83c656015541feea3f8e71361c9225", + "blockNumber": "0x210", + "blockTimestamp": "0x6a5dfabc", + "transactionHash": "0x06397c2330f618702471ba69253795a9d0f2e428e29de3dfefec02db1911b1ef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7fde866c72dfa26e56e43de9e6a330e34c83c656015541feea3f8e71361c9225", + "blockNumber": "0x210", + "blockTimestamp": "0x6a5dfabc", + "transactionHash": "0x06397c2330f618702471ba69253795a9d0f2e428e29de3dfefec02db1911b1ef", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881", + "0x0000000000000000000000008e4990ef899d2fb0cac52be4d3813a0543ad4431" + ], + "data": "0x", + "blockHash": "0x3d50337d18ab6f14d700e3135d51817f942ee73101dfdd25f9fff53394f518b2", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dfabc", + "transactionHash": "0xdc331fae646725182f71d6a9398f50540c4a50fc7e1181a7b2afa35e97377a93", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000002582d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3d50337d18ab6f14d700e3135d51817f942ee73101dfdd25f9fff53394f518b2", + "blockNumber": "0x212", + "blockTimestamp": "0x6a5dfabc", + "transactionHash": "0xdc331fae646725182f71d6a9398f50540c4a50fc7e1181a7b2afa35e97377a93", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" + ], + "data": "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb46167b476c2f50d2f66d5fdded09e641da8823d205245a4cd28add04af31eb0", + "blockNumber": "0x213", + "blockTimestamp": "0x6a5dfabd", + "transactionHash": "0x1a95702be29f520ec5a6ea9f63e65de55dae85b6396100abce54057f55d2e9d1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x73efe5e66e545a5f1fb52054094f31907ac9a2c1608c66546d843feff7e48c81", + "blockNumber": "0x215", + "blockTimestamp": "0x6a5dfabd", + "transactionHash": "0x571d27533cd48fc86dbefc4ddde54cbe30192adb69c0392168f354ec3173b9e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a", + "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e", + "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef" + ], + "data": "0x212283bc1af796a34213eb18e98a113d32f9ea8b4944a5af675b7670c2e5d0e7", + "blockHash": "0x73efe5e66e545a5f1fb52054094f31907ac9a2c1608c66546d843feff7e48c81", + "blockNumber": "0x215", + "blockTimestamp": "0x6a5dfabd", + "transactionHash": "0x571d27533cd48fc86dbefc4ddde54cbe30192adb69c0392168f354ec3173b9e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x212283bc1af796a34213eb18e98a113d32f9ea8b4944a5af675b7670c2e5d0e7e710743051fe19bd9c88126f970e6f7272855940f855e3ea04ad415c27486fe5", + "blockHash": "0x0dbd12ecb499607c86804b432aa577895f637cf7a5d05952b91c2d9337d6fac6", + "blockNumber": "0x216", + "blockTimestamp": "0x6a5dfabf", + "transactionHash": "0x7ef5d961f47b198f1956ad27b6b6ebead56bdf0ecce29ed338d55cd6ad9c8db7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0dbd12ecb499607c86804b432aa577895f637cf7a5d05952b91c2d9337d6fac6", + "blockNumber": "0x216", + "blockTimestamp": "0x6a5dfabf", + "transactionHash": "0x7ef5d961f47b198f1956ad27b6b6ebead56bdf0ecce29ed338d55cd6ad9c8db7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xbf674b2dacad2be0bd35165fe8d338711a7788f77df3914033d79c34373f169bb3718afeb8182524f7a0af7f95ceb5ccb5e5b9a35a3dbcb52259143e606783c9", + "blockHash": "0xc2cdb9e28e29d9266bb30f16d7cb7640eba15a7f5dd170d74c9d707c5f8ade94", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dfabf", + "transactionHash": "0x8af78e5451aa8b2f9223aebebb7283cdc7b77107f99d1e076e92d2bf976d8d29", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc2cdb9e28e29d9266bb30f16d7cb7640eba15a7f5dd170d74c9d707c5f8ade94", + "blockNumber": "0x218", + "blockTimestamp": "0x6a5dfabf", + "transactionHash": "0x8af78e5451aa8b2f9223aebebb7283cdc7b77107f99d1e076e92d2bf976d8d29", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xb3718afeb8182524f7a0af7f95ceb5ccb5e5b9a35a3dbcb52259143e606783c958090a5b58f6621d5a9a4c1e9fae57cc435296683419fbcb3d40e240bd3f63f9", + "blockHash": "0x999b69bf9038f22787f4aeb97b24fc22b5b8b0af1b5d50005ceab333b7e208a8", + "blockNumber": "0x219", + "blockTimestamp": "0x6a5dfac1", + "transactionHash": "0xaddaa982d54bcb048da3cf9d01cc4e0242ce5234c9b4f7c5b44fbbd7f2a6fb54", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x999b69bf9038f22787f4aeb97b24fc22b5b8b0af1b5d50005ceab333b7e208a8", + "blockNumber": "0x219", + "blockTimestamp": "0x6a5dfac1", + "transactionHash": "0xaddaa982d54bcb048da3cf9d01cc4e0242ce5234c9b4f7c5b44fbbd7f2a6fb54", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xa4bd461d3dcbfecd4c1505f5d94f5a04e1713a777a254231e2602d9daf34e9a8f3ea743772203c1c29d75e901b516c2ec36458a85ca177734239283df828b85f", + "blockHash": "0xd1df5a27bcdc2c2288f102b40028daf91056abf06d842c782b12b5b54e68a286", + "blockNumber": "0x21b", + "blockTimestamp": "0x6a5dfac2", + "transactionHash": "0x897929a13f60ab11b8fc847404404987104c749f318c02dccb95b3d729c57d77", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd1df5a27bcdc2c2288f102b40028daf91056abf06d842c782b12b5b54e68a286", + "blockNumber": "0x21b", + "blockTimestamp": "0x6a5dfac2", + "transactionHash": "0x897929a13f60ab11b8fc847404404987104c749f318c02dccb95b3d729c57d77", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf3ea743772203c1c29d75e901b516c2ec36458a85ca177734239283df828b85f0a58c8a1e0f70607f5c5155ed30d5cec3f6f3b349a17a89bc5b10a4fc8dde221", + "blockHash": "0xd0ea9dd8763b4959d8eaae20f9a5a38bf2b90bf87d35f53fec63d9cc819c8d56", + "blockNumber": "0x21c", + "blockTimestamp": "0x6a5dfac2", + "transactionHash": "0xf13917fc435020cf8a49542f53bde606d844f42ebe9de996df59bac63caa69d3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd0ea9dd8763b4959d8eaae20f9a5a38bf2b90bf87d35f53fec63d9cc819c8d56", + "blockNumber": "0x21c", + "blockTimestamp": "0x6a5dfac2", + "transactionHash": "0xf13917fc435020cf8a49542f53bde606d844f42ebe9de996df59bac63caa69d3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x1a9ac08428ec806e51ca8f71379b1d2a4ce6a4138d70c59fb374736297b4ed528490c30cba5c743759ee0ff1e33893e65dbaaa7b5b4686a90ec36b6b62da638d", + "blockHash": "0x6598c971c5f9b6b5c6e7aafe56e9f83995adf1e67a6cee3ab4e516b0a37230ee", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dfac3", + "transactionHash": "0x30ab353a3372af3d5b33b9dd2345957b144dd4cb2159b6920a3816279db3c11d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6598c971c5f9b6b5c6e7aafe56e9f83995adf1e67a6cee3ab4e516b0a37230ee", + "blockNumber": "0x21e", + "blockTimestamp": "0x6a5dfac3", + "transactionHash": "0x30ab353a3372af3d5b33b9dd2345957b144dd4cb2159b6920a3816279db3c11d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x8490c30cba5c743759ee0ff1e33893e65dbaaa7b5b4686a90ec36b6b62da638d9173f94f2fb97eef56ba6fc43925899cc7aabc7daf485ef14f5de621ac79055d", + "blockHash": "0xacf058a64315e9c2770082fc4a8c9b19d49f318e968e2cd38e22823626e016b6", + "blockNumber": "0x21f", + "blockTimestamp": "0x6a5dfac4", + "transactionHash": "0x4ecbc03c000e126f4062ea3b4344851bcbb9d3d0af3a137a67d170b832004af0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xacf058a64315e9c2770082fc4a8c9b19d49f318e968e2cd38e22823626e016b6", + "blockNumber": "0x21f", + "blockTimestamp": "0x6a5dfac4", + "transactionHash": "0x4ecbc03c000e126f4062ea3b4344851bcbb9d3d0af3a137a67d170b832004af0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x159230bfb0324ac9328207a68321ad4e28cee2025735dd5f0e27cddc9ee018e2489c6b428f50828ff1e54dc32b80ec6793da8e775c5f308d98a176d3f3af40ae", + "blockHash": "0xf2f419dbac87acf8a3bf9f780925201bc164813e78bd0e4745ff9b20d112ba63", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dfac5", + "transactionHash": "0xfb63dddfe78597022fd673326a2e6c67b32bd90414d0491e6b90524247611f14", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf2f419dbac87acf8a3bf9f780925201bc164813e78bd0e4745ff9b20d112ba63", + "blockNumber": "0x221", + "blockTimestamp": "0x6a5dfac5", + "transactionHash": "0xfb63dddfe78597022fd673326a2e6c67b32bd90414d0491e6b90524247611f14", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xc6f1c02a188ce786f5a62481f14c189b8a6c36756a47c5d0c687f81f9a899f507edd771879c5c45249a814ae964960b87b3bd6df7da0b20e177b80a3f3959c2a", + "blockHash": "0x34ccef6ac18515642937eff06675557d42a1adad2e691358b7c5a7334817f744", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dfac5", + "transactionHash": "0x44dbc7214d40e9275cf669589a5d19093df0f97b27c9be8ac7f5cea7bab4a9e3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x34ccef6ac18515642937eff06675557d42a1adad2e691358b7c5a7334817f744", + "blockNumber": "0x222", + "blockTimestamp": "0x6a5dfac5", + "transactionHash": "0x44dbc7214d40e9275cf669589a5d19093df0f97b27c9be8ac7f5cea7bab4a9e3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x55dd2594d4377713cbeeae46fdd8cc1addf9b635447b5ccb86dc84acd3ddbe2cac1baafec3ed4ae09996488a93ed34cb2149b37362f0824101017b9f0fe144d7", + "blockHash": "0x4de4a811a78dae9848592c556aef30181b9d09573b7c82d26ed07ffe05308ab8", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dfac6", + "transactionHash": "0xcfa65979822c658b6493a93646924654fcfe3980f59181717e5d077eb87d0e34", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4de4a811a78dae9848592c556aef30181b9d09573b7c82d26ed07ffe05308ab8", + "blockNumber": "0x224", + "blockTimestamp": "0x6a5dfac6", + "transactionHash": "0xcfa65979822c658b6493a93646924654fcfe3980f59181717e5d077eb87d0e34", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x6b853b3bcbee314f2bf4086391e1a1988060af063328df9f95c6d9566f9fb3c729fb08620d76ec5be88f9abda6a715d2477a4f49f4203a9711532f929c065510", + "blockHash": "0xfc42d9ff6ef3d7023da070c773ee3fe376b7c6742e2b430e52cfc419538bfafb", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dfac7", + "transactionHash": "0x9d83784bd38683481f5ed3c62367e0f4b29778ddf0b3094a7b35d3fe04de8831", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfc42d9ff6ef3d7023da070c773ee3fe376b7c6742e2b430e52cfc419538bfafb", + "blockNumber": "0x225", + "blockTimestamp": "0x6a5dfac7", + "transactionHash": "0x9d83784bd38683481f5ed3c62367e0f4b29778ddf0b3094a7b35d3fe04de8831", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf9d31d7058d2acc7c04cf77e9be54da518ae1e3ebab70d8b9e76e08397a21bbcd439bc4ed00022367503e745162a2f78d764e4ca8ce0022add34d782f326521d", + "blockHash": "0x5cc16a55bfd784eb9eee2bec0832cbf1f4edea694b131c456871efb538c1d516", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dfac8", + "transactionHash": "0xface15d715e20ab98a6f7cb1527378662fd3c3915eac046716fd6f65057413eb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5cc16a55bfd784eb9eee2bec0832cbf1f4edea694b131c456871efb538c1d516", + "blockNumber": "0x227", + "blockTimestamp": "0x6a5dfac8", + "transactionHash": "0xface15d715e20ab98a6f7cb1527378662fd3c3915eac046716fd6f65057413eb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x5179b31126a75ac5c6aaad921a1c2c4915d37b319f6f4aa834e19fcde153b13e55c1811bab335ca8c37318770b4f739bc8d4632bd0e808fe88c72fbd579b2225", + "blockHash": "0xb209ed65d2955ba4c1c59f49e6118f1741408ccc18b3ce3e76f68ada715399a7", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dfac9", + "transactionHash": "0x749ece20bcdd96cd4a07f46d70c0bee475352bca4502ccb764c14c24f81a1fe0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb209ed65d2955ba4c1c59f49e6118f1741408ccc18b3ce3e76f68ada715399a7", + "blockNumber": "0x228", + "blockTimestamp": "0x6a5dfac9", + "transactionHash": "0x749ece20bcdd96cd4a07f46d70c0bee475352bca4502ccb764c14c24f81a1fe0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x50e14e3a99ccc49ec20f9ad9f2da03f46d437124e4384d3b367da45885fb0f2414be422e2b39e32a5ca093be40341bd2de5d2365f3b81e47e72a60fb882a537e", + "blockHash": "0xa4ff4fe4a3a5dd1a4ed62f2cb286d359687c4ac8c66d96639c33b38ffb4e0fce", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dfaca", + "transactionHash": "0x97a8efcad7679b8d033ff3c950b6b4d73c0539452c0c2e634e9592205c1be217", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa4ff4fe4a3a5dd1a4ed62f2cb286d359687c4ac8c66d96639c33b38ffb4e0fce", + "blockNumber": "0x22a", + "blockTimestamp": "0x6a5dfaca", + "transactionHash": "0x97a8efcad7679b8d033ff3c950b6b4d73c0539452c0c2e634e9592205c1be217", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x8cbdb2bff812280b22c86c88904be0872214f659292fd6a40cd62cc43102c171d5a77365c6b697e1c48d2d77ec12b60a09b0fe559b09622e0a2576a40468c228", + "blockHash": "0xa5addb378a0c0bee24ac691dc7dd110070515cfc13430f20011b2a5d30e3bcc1", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dfaca", + "transactionHash": "0x92722363484e872a84107b3487bd5f3fa2864fd5999f4237e7e5c092fe23c45e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa5addb378a0c0bee24ac691dc7dd110070515cfc13430f20011b2a5d30e3bcc1", + "blockNumber": "0x22b", + "blockTimestamp": "0x6a5dfaca", + "transactionHash": "0x92722363484e872a84107b3487bd5f3fa2864fd5999f4237e7e5c092fe23c45e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x46e91db90736fff97c3a74d75c09de0e1d9f10d6ebe91e173aff38bd384392fa6ff2c2c0cd789e6b5928356f19a99b7a67b813b54e30c1d8bad7a305efb589a3", + "blockHash": "0x55dc3e2d09dba970abe34ae57c8fcf2401da83a770bcc3d9b83a61175885dbfb", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dfacb", + "transactionHash": "0x1aa9aa4845201bb6400daa964b3033001f4a041a4018249bdac79dee68c60dff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x55dc3e2d09dba970abe34ae57c8fcf2401da83a770bcc3d9b83a61175885dbfb", + "blockNumber": "0x22d", + "blockTimestamp": "0x6a5dfacb", + "transactionHash": "0x1aa9aa4845201bb6400daa964b3033001f4a041a4018249bdac79dee68c60dff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xfaf4d4d5dcac5a767078ba8273fb65a0de6387ffc1ee2b565229cbf03b3c73201a85dc955df5272e23d3bbe0419c618cf3958a2a5d9fa8c50233493170fb764b", + "blockHash": "0x75d4c026136faca9e622ebdc2acf58e788390139ae0c34b1c7af33e78183d95b", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dfacc", + "transactionHash": "0xcb9e7139da897b2dd92d2d1c4fde80c1e23db7b4d92ffcf14912045e77ac9905", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x75d4c026136faca9e622ebdc2acf58e788390139ae0c34b1c7af33e78183d95b", + "blockNumber": "0x22e", + "blockTimestamp": "0x6a5dfacc", + "transactionHash": "0xcb9e7139da897b2dd92d2d1c4fde80c1e23db7b4d92ffcf14912045e77ac9905", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x523dd5a2aa53d58a0783f7d387a563ac28f1ed69d4d1faacc38faaa28b17598c599a9971be62e9b3c482a690c95012ac2739841ef6cf3e87872d6b63d29c04c8", + "blockHash": "0x8ab8f570c23ae429e8132296d167d8f1da2f0dbfb43cf0134dcfded6b425101a", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dfacd", + "transactionHash": "0xee2ce0f689379b8be38110d5f08dce2cd70bd10d4856647d8355cef6e4648d32", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8ab8f570c23ae429e8132296d167d8f1da2f0dbfb43cf0134dcfded6b425101a", + "blockNumber": "0x230", + "blockTimestamp": "0x6a5dfacd", + "transactionHash": "0xee2ce0f689379b8be38110d5f08dce2cd70bd10d4856647d8355cef6e4648d32", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf689486fd7025ee0c4a33c1e7ec310f0810e51251ab7197644f344cabf2d67d9fa0942ccf3b9486e854eb4e794e0ef4c8b3691a1a1069ed55b901bc9378aaafc", + "blockHash": "0xf44667634523525f348028283d0f54ed85926da7a45e93ac4b398fe5798cbee2", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dface", + "transactionHash": "0x60d2f7dfc33266a433a8b20139bf385804f4c9c75318e3ca4f121ee85b92259d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf44667634523525f348028283d0f54ed85926da7a45e93ac4b398fe5798cbee2", + "blockNumber": "0x231", + "blockTimestamp": "0x6a5dface", + "transactionHash": "0x60d2f7dfc33266a433a8b20139bf385804f4c9c75318e3ca4f121ee85b92259d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xea0badf5a5a158370316a3bbc508cef75a4a216a97b18f36de50036bea2ff2060eb28f62864a84dcdd476d70df8fde77539d8c9cddd912c3948b91fb0813d980", + "blockHash": "0xa402ce01e74d982e25f644ff4eb5de645dc8eebae456fdf29be2e92615ff7dd2", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dface", + "transactionHash": "0xbb993132bfac9f142f90e991ddb8c628751c12f9f26ba76ffb50e10b18acad43", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa402ce01e74d982e25f644ff4eb5de645dc8eebae456fdf29be2e92615ff7dd2", + "blockNumber": "0x233", + "blockTimestamp": "0x6a5dface", + "transactionHash": "0xbb993132bfac9f142f90e991ddb8c628751c12f9f26ba76ffb50e10b18acad43", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x707df0ef49e292d02e43cb33e0b203c1dee169144f82f532c5f7c67604e4e4eac8089ccd49d60350a5df7c2038f1a3d67134372dd36662a85bb71a9401a553e9", + "blockHash": "0x72b2a724b29b05be1e91fdc956be4d8220672c466b2f5cbbe01e188b53ae23fd", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dfacf", + "transactionHash": "0x5d02e139d17b416d4a7ef9166c3794a8c93432d8274a1493cefa5edd79df3440", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x72b2a724b29b05be1e91fdc956be4d8220672c466b2f5cbbe01e188b53ae23fd", + "blockNumber": "0x234", + "blockTimestamp": "0x6a5dfacf", + "transactionHash": "0x5d02e139d17b416d4a7ef9166c3794a8c93432d8274a1493cefa5edd79df3440", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xa65a9e3c95a7d90d561607071acad31f2aeaaa081840aa56599507e1d5c7ef193c00790459123e1fb955ad8a3acd051281426293d4ada112b557412fd865ab6a", + "blockHash": "0x92ea1471a56c1f21bcb84a7b22736c7a586d4ecdf0020ed770fa7a2812d3f0c5", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dfad0", + "transactionHash": "0xf6ba9290b92d307051ca83a0b61fc6522e382d9840740ba31a9401fc827dd625", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x92ea1471a56c1f21bcb84a7b22736c7a586d4ecdf0020ed770fa7a2812d3f0c5", + "blockNumber": "0x236", + "blockTimestamp": "0x6a5dfad0", + "transactionHash": "0xf6ba9290b92d307051ca83a0b61fc6522e382d9840740ba31a9401fc827dd625", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x230a24ac4fe7a1a36215a300ea61f491f5a7bfa7896aa6afc73eda7d0908607647e456b343f32ee1d9b2408cac97e9dad6d63d6dde6bdf33eb7708be795ff5af", + "blockHash": "0xeda405ff4f06f2b04a12c2dabb19816031d14ad361e317c6a13c99163d2de7dc", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dfad1", + "transactionHash": "0x5a4bc7d787e01978e097e49843d8076557c6e01491d75fa99cd295c137cf9cb7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xeda405ff4f06f2b04a12c2dabb19816031d14ad361e317c6a13c99163d2de7dc", + "blockNumber": "0x237", + "blockTimestamp": "0x6a5dfad1", + "transactionHash": "0x5a4bc7d787e01978e097e49843d8076557c6e01491d75fa99cd295c137cf9cb7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xe5f5da2f5984bd0a29ea398b8a0a8ea300c17438a1677a7154bf111a5d744af6e846d3810a68181fbbd44961be6de5f90ac02fd7f1e23e4ef763a22e192defbc", + "blockHash": "0xa1d02032efb3faba074796588e09513dc72cc9a25e6c59356918829677df7583", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dfad2", + "transactionHash": "0x740e8a5ed51049b7bf6593d7216b7a5b196c0420d5b00fe3e1fc83cd044b3c10", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa1d02032efb3faba074796588e09513dc72cc9a25e6c59356918829677df7583", + "blockNumber": "0x239", + "blockTimestamp": "0x6a5dfad2", + "transactionHash": "0x740e8a5ed51049b7bf6593d7216b7a5b196c0420d5b00fe3e1fc83cd044b3c10", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x503576d6278c99a4ec800e90f4d117e6c0b36565097e2ebe7ba9053e60da1a3153b260feab1d79ba557d184c909d72dc758328cabc789c01f04613f5e3c9e993", + "blockHash": "0x702d4b93c77f52c00ab4b8c46a67eaf66187ab59be3d8d844070520482467814", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dfad2", + "transactionHash": "0x4db6a7b7662daa437457fe7587faabee6a9fdffd0a3ae8b51f14440db86e7198", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x702d4b93c77f52c00ab4b8c46a67eaf66187ab59be3d8d844070520482467814", + "blockNumber": "0x23a", + "blockTimestamp": "0x6a5dfad2", + "transactionHash": "0x4db6a7b7662daa437457fe7587faabee6a9fdffd0a3ae8b51f14440db86e7198", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x432f8c7f70e8a29fef284123b222053360bf1a0daf5a2d13ad8ac3ba0cdf8f72850e0a39292cbab09f74da0577674550bf9932a318a365f962fea1f1af9e5de6", + "blockHash": "0xfbf7afae14ba5cdef12c6e0f21e60f3124e60c08e17b090d1df99678ecf676c8", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dfad3", + "transactionHash": "0x3cb95c27c9f9899192fa5e21c3d6040d7dfa6cfa9a092464935b76a235c5eee3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfbf7afae14ba5cdef12c6e0f21e60f3124e60c08e17b090d1df99678ecf676c8", + "blockNumber": "0x23c", + "blockTimestamp": "0x6a5dfad3", + "transactionHash": "0x3cb95c27c9f9899192fa5e21c3d6040d7dfa6cfa9a092464935b76a235c5eee3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xde76d6763324dbc1311b83248592e1c769a515360e1d76d45c8dbcd77c7ff20c", + "blockNumber": "0x23d", + "blockTimestamp": "0x6a5dfad4", + "transactionHash": "0xce014d54c57615f42ab9cfbfd3efed7ef454314cb87a5944eaa186c0ec1ac801", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a", + "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e", + "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0x6446c58789f72bdc64fd03e90ba469d75ddcf67025b39dda07ce619fa58ab775", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dfad5", + "transactionHash": "0xef9782fcdd32567b056042ac61c4d4ea522ad4ab55dea922f5eaaf57b9b40d1e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x8e4990ef899d2fb0cac52be4d3813a0543ad4431", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003854e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6446c58789f72bdc64fd03e90ba469d75ddcf67025b39dda07ce619fa58ab775", + "blockNumber": "0x23f", + "blockTimestamp": "0x6a5dfad5", + "transactionHash": "0xef9782fcdd32567b056042ac61c4d4ea522ad4ab55dea922f5eaaf57b9b40d1e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000006a5dfad667dfa6844dc4772e8b10b67fafef8a2cb5c5984d8ce0cd5f03bd8e39ec1369f900000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf1d04a767bf1cf785525060b85a728563e7f37b6fd61d4936982c56af8e26d87", + "blockNumber": "0x240", + "blockTimestamp": "0x6a5dfad6", + "transactionHash": "0x45dae5ab566b1c15e487e89473d43004c260bd1daf20ee380d476eb05f2933a1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000241000000000000000000000000000000000000000000000000000000006a5dfad6fadae00725d4142254dda97261c137870f93035531dcfa0c5729f0d302b7933e00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x248426be105740cc593c9baceecfd019bfd8d756b411b166732ea92265fdaeb1", + "blockNumber": "0x241", + "blockTimestamp": "0x6a5dfad6", + "transactionHash": "0x8c40c60b93f6ab8700fbfe837ea0ba1810fafee92bfb1d5ce9a788fa09723eb7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000242000000000000000000000000000000000000000000000000000000006a5dfad64e835241e20aba285ef1780913bd181fbb988ac66bdd8c128dcf31e793b4869600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x54b5909c7873e28445eab3393c4188e180ff391cc6671f98dabe692c20ad9c79", + "blockNumber": "0x242", + "blockTimestamp": "0x6a5dfad6", + "transactionHash": "0x1f55563cb275aa8fe189cf77dcce5fb8a4b169fda96dbdebc5b20da3d92263dd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x70820d880588d0bc1091d8e08c4002d52f4da752b001ed24d7fe2c7bb4343881", + "0x96d30e859fad055ab8e02b8255226b0b2290dc4e2c5c97beafa1aa220e8aebf5", + "0x4cba036a41b6fc90be13313248640e6df05ab6aaf9dddddd7beeadddcef49fff" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xc69fefaad621efcd39b6f6ab0674c9a1ae0da0e816ac03d664164dd08f73d014", + "blockNumber": "0x343", + "blockTimestamp": "0x6a5dfadb", + "transactionHash": "0x0f19569378e47bffa3acb2d01284137ca22ffa9e07e178ebfe650e1f48f8f3b6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6b60a0ce2a78c4458ea31a3ec7dde3ac62172ad7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc69fefaad621efcd39b6f6ab0674c9a1ae0da0e816ac03d664164dd08f73d014", + "blockNumber": "0x343", + "blockTimestamp": "0x6a5dfadb", + "transactionHash": "0x0f19569378e47bffa3acb2d01284137ca22ffa9e07e178ebfe650e1f48f8f3b6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x559a4c54b7c5da67c5f203e0a4a83223aa18ac0921ab5a2fb2862c5f7b0d4208", + "blockNumber": "0x344", + "blockTimestamp": "0x6a5dfadc", + "transactionHash": "0x31d6f550efa2ebd07fd051d1bbd2985cc4a9bad76efefc95f8d9968914124c8e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x914f0de70a0f8337e9d4b52b8d5600b0664b35e4", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001ba9c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x559a4c54b7c5da67c5f203e0a4a83223aa18ac0921ab5a2fb2862c5f7b0d4208", + "blockNumber": "0x344", + "blockTimestamp": "0x6a5dfadc", + "transactionHash": "0x31d6f550efa2ebd07fd051d1bbd2985cc4a9bad76efefc95f8d9968914124c8e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0xd4c1f26596106b1e2415fd16aa1381cfc2784107f23827513c64d85556a4dd13", + "blockNumber": "0x345", + "blockTimestamp": "0x6a5dfadd", + "transactionHash": "0xc626d2ff618e20a51c3d4e09c192d82305b7d20e4233d98ab37b3a26c5a8e4a9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x00000000000000000000000098bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "blockHash": "0x0612421183dce93a9d333161357595c06687b8e7c592c51b2db19b31fb2cad43", + "blockNumber": "0x346", + "blockTimestamp": "0x6a5dfadd", + "transactionHash": "0xb20f0b1a119d6237c423b0f6734cfd49d9a6e9255dfd0d6b9151b37f0ebd316d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000701c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c600000000000000000000000098bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "blockHash": "0x0612421183dce93a9d333161357595c06687b8e7c592c51b2db19b31fb2cad43", + "blockNumber": "0x346", + "blockTimestamp": "0x6a5dfadd", + "transactionHash": "0xb20f0b1a119d6237c423b0f6734cfd49d9a6e9255dfd0d6b9151b37f0ebd316d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x3361cd8fe853669ba262ee235caa688a328e661c6a9ca5faa2153e09d3dacc56", + "blockNumber": "0x349", + "blockTimestamp": "0x6a5dfae6", + "transactionHash": "0x717e5cd102d10554bbac4ab62e406021dd79de10e6be4180c316a3d6a754fc97", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x3e9f99b210dbcebb3300a4add50b7b3bd17eaa798099b6f93488bd9c2ea5a010", + "blockNumber": "0x34a", + "blockTimestamp": "0x6a5dfae6", + "transactionHash": "0xbc1a2dbc63b8f33340b14b211c455b57e99d90feab8e7b47443bcd843612a3ef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf3601c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x00fe64e2a7bd23bd758d6e00529dcc98f13e066f12c4fdd82826eaef7070a2da", + "blockNumber": "0x34f", + "blockTimestamp": "0x6a5dfae9", + "transactionHash": "0xcd58c755c8232ad53bb21b99ec3e9dcb3ec36631e1837336c33292e36ce7e967", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7b", + "blockHash": "0x00fe64e2a7bd23bd758d6e00529dcc98f13e066f12c4fdd82826eaef7070a2da", + "blockNumber": "0x34f", + "blockTimestamp": "0x6a5dfae9", + "transactionHash": "0xcd58c755c8232ad53bb21b99ec3e9dcb3ec36631e1837336c33292e36ce7e967", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7bfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442", + "blockHash": "0xb5402f88c34a184be02c69b6e4c3e4b463e6f7f00042a102ab9217bb67eecff1", + "blockNumber": "0x351", + "blockTimestamp": "0x6a5dfaea", + "transactionHash": "0x18c1bae83d743fec7b53cad1ed13cfa44f5351b37567c4ed5c87a554aad7e141", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb5402f88c34a184be02c69b6e4c3e4b463e6f7f00042a102ab9217bb67eecff1", + "blockNumber": "0x351", + "blockTimestamp": "0x6a5dfaea", + "transactionHash": "0x18c1bae83d743fec7b53cad1ed13cfa44f5351b37567c4ed5c87a554aad7e141", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442c28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe317", + "blockHash": "0x1eba6f278860a6fd1d90dc33829afec8a77356b343654d982bfcc801715fe328", + "blockNumber": "0x352", + "blockTimestamp": "0x6a5dfaea", + "transactionHash": "0xc446eab7511ae4b2e7f0c8a78f94c74e9474351c06f82059e9a968983aaf286d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1eba6f278860a6fd1d90dc33829afec8a77356b343654d982bfcc801715fe328", + "blockNumber": "0x352", + "blockTimestamp": "0x6a5dfaea", + "transactionHash": "0xc446eab7511ae4b2e7f0c8a78f94c74e9474351c06f82059e9a968983aaf286d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe3176c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd602", + "blockHash": "0x56ad1dcf8a32239b3c56c213dfced2563b2120727dbb991cd70c34ed67ae7048", + "blockNumber": "0x354", + "blockTimestamp": "0x6a5dfaeb", + "transactionHash": "0x47bd2d1d9c0ef43d5a32524ab9f7204a7a4bcbc02c167f20ea72f73a4d77f560", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x56ad1dcf8a32239b3c56c213dfced2563b2120727dbb991cd70c34ed67ae7048", + "blockNumber": "0x354", + "blockTimestamp": "0x6a5dfaeb", + "transactionHash": "0x47bd2d1d9c0ef43d5a32524ab9f7204a7a4bcbc02c167f20ea72f73a4d77f560", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd6023080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a1248", + "blockHash": "0xff639df6405ffe40a448b4e11426d062f488e380f2865e743f503a29ee5a0c00", + "blockNumber": "0x355", + "blockTimestamp": "0x6a5dfaec", + "transactionHash": "0x95a8b62961d69d5f14dbd612673541b8b6edec19a239565f6d25045d5f7495c9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xff639df6405ffe40a448b4e11426d062f488e380f2865e743f503a29ee5a0c00", + "blockNumber": "0x355", + "blockTimestamp": "0x6a5dfaec", + "transactionHash": "0x95a8b62961d69d5f14dbd612673541b8b6edec19a239565f6d25045d5f7495c9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a12487df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0ca", + "blockHash": "0x52b4c2d5f3f57904f4c5cf7068238a4836f7f142f628c943b386a827dd345f89", + "blockNumber": "0x357", + "blockTimestamp": "0x6a5dfaec", + "transactionHash": "0x3855100a86096c307901b2e084a30129175f8404fe96949a288a976e5bb50ab1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x52b4c2d5f3f57904f4c5cf7068238a4836f7f142f628c943b386a827dd345f89", + "blockNumber": "0x357", + "blockTimestamp": "0x6a5dfaec", + "transactionHash": "0x3855100a86096c307901b2e084a30129175f8404fe96949a288a976e5bb50ab1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x7df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0caba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c0", + "blockHash": "0xb8e8c4b4ec09cf0548b32fba453d9c74c341de4eb887490fe506cc51344a758d", + "blockNumber": "0x358", + "blockTimestamp": "0x6a5dfaed", + "transactionHash": "0xc9db9e936c7e204691f85c46963eeb54211cd697747f5eeff4ca8b2756b97311", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb8e8c4b4ec09cf0548b32fba453d9c74c341de4eb887490fe506cc51344a758d", + "blockNumber": "0x358", + "blockTimestamp": "0x6a5dfaed", + "transactionHash": "0xc9db9e936c7e204691f85c46963eeb54211cd697747f5eeff4ca8b2756b97311", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c010f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583", + "blockHash": "0x40be49c0f1c9746230bc8f289aa4608e77b5f1f95f200570b383f0b423a96ada", + "blockNumber": "0x35a", + "blockTimestamp": "0x6a5dfaed", + "transactionHash": "0x8423e1305280fe167e6e17bc9912f7740b25fbbea29e605301a27fff37cff698", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x40be49c0f1c9746230bc8f289aa4608e77b5f1f95f200570b383f0b423a96ada", + "blockNumber": "0x35a", + "blockTimestamp": "0x6a5dfaed", + "transactionHash": "0x8423e1305280fe167e6e17bc9912f7740b25fbbea29e605301a27fff37cff698", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x10f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583b502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb416735601", + "blockHash": "0x8f62613c2e5ebf8357ec1a1134e75791437e04fd76807b862262cf8453e9743b", + "blockNumber": "0x35b", + "blockTimestamp": "0x6a5dfaee", + "transactionHash": "0xe034abee9138d8ac4cccf9cf4a4feea2d1aa5217c287bc81d47f3f1065388ca1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8f62613c2e5ebf8357ec1a1134e75791437e04fd76807b862262cf8453e9743b", + "blockNumber": "0x35b", + "blockTimestamp": "0x6a5dfaee", + "transactionHash": "0xe034abee9138d8ac4cccf9cf4a4feea2d1aa5217c287bc81d47f3f1065388ca1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb4167356019a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6", + "blockHash": "0xe7f3d0bd4eaa0279e5a798cab6f1e8c2c8e47d976196f4f1639fc3daea379e30", + "blockNumber": "0x35d", + "blockTimestamp": "0x6a5dfaef", + "transactionHash": "0x21e7d8588cdc77a6550466a0783bfcb0e00aa9ae1f5a049d980f3f8de6168dd5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe7f3d0bd4eaa0279e5a798cab6f1e8c2c8e47d976196f4f1639fc3daea379e30", + "blockNumber": "0x35d", + "blockTimestamp": "0x6a5dfaef", + "transactionHash": "0x21e7d8588cdc77a6550466a0783bfcb0e00aa9ae1f5a049d980f3f8de6168dd5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6d95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296", + "blockHash": "0x3deec4191abb2f92fa8e27125b54aa52ff6b451a0515597127d92922256cec6e", + "blockNumber": "0x35e", + "blockTimestamp": "0x6a5dfaef", + "transactionHash": "0x017704ad933ab3d402429489b71cef5ae1405865c748f3deb7ed9f828b473834", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3deec4191abb2f92fa8e27125b54aa52ff6b451a0515597127d92922256cec6e", + "blockNumber": "0x35e", + "blockTimestamp": "0x6a5dfaef", + "transactionHash": "0x017704ad933ab3d402429489b71cef5ae1405865c748f3deb7ed9f828b473834", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1dea", + "blockHash": "0x68c01318cc64cc615edcd1ad57d246737c40c8b4523f40d04d77d20a21587911", + "blockNumber": "0x360", + "blockTimestamp": "0x6a5dfaf0", + "transactionHash": "0xc556ad034e2ef0d9451534f5e81d35a16d4adba025c282352e9b03b1a9ef7368", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x68c01318cc64cc615edcd1ad57d246737c40c8b4523f40d04d77d20a21587911", + "blockNumber": "0x360", + "blockTimestamp": "0x6a5dfaf0", + "transactionHash": "0xc556ad034e2ef0d9451534f5e81d35a16d4adba025c282352e9b03b1a9ef7368", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1deaa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f171", + "blockHash": "0xbaa0e2024fee238245b724c3fba53258644dd8504e9e2b51a709cc77cd7638c0", + "blockNumber": "0x361", + "blockTimestamp": "0x6a5dfaf1", + "transactionHash": "0x4726276f6946962872cdb09a1373c9561e6611fecad30d1693a83a7fe13bd3fa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbaa0e2024fee238245b724c3fba53258644dd8504e9e2b51a709cc77cd7638c0", + "blockNumber": "0x361", + "blockTimestamp": "0x6a5dfaf1", + "transactionHash": "0x4726276f6946962872cdb09a1373c9561e6611fecad30d1693a83a7fe13bd3fa", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f1714397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102", + "blockHash": "0x62e9a6582ef961f31e18ea7a1d01b532dc274dbc1f6902c2d53b49a8293e0d2c", + "blockNumber": "0x363", + "blockTimestamp": "0x6a5dfaf1", + "transactionHash": "0xcaba091f842ac8b871609798a7d81b951d79f8ed00a1e019118209a994d41a09", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x62e9a6582ef961f31e18ea7a1d01b532dc274dbc1f6902c2d53b49a8293e0d2c", + "blockNumber": "0x363", + "blockTimestamp": "0x6a5dfaf1", + "transactionHash": "0xcaba091f842ac8b871609798a7d81b951d79f8ed00a1e019118209a994d41a09", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102ff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a87", + "blockHash": "0x828cae7f1dd1160642a81e37eed8886c4616ec3dfb637b9c6f4b83af4f11abe7", + "blockNumber": "0x364", + "blockTimestamp": "0x6a5dfaf2", + "transactionHash": "0x1320f62813cc1a5183cae566019afbfd6c1e431f37f8c9c8bb6a397b83bd2faf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x828cae7f1dd1160642a81e37eed8886c4616ec3dfb637b9c6f4b83af4f11abe7", + "blockNumber": "0x364", + "blockTimestamp": "0x6a5dfaf2", + "transactionHash": "0x1320f62813cc1a5183cae566019afbfd6c1e431f37f8c9c8bb6a397b83bd2faf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a8715797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d84", + "blockHash": "0x6da590ecdc6dffebffcc312e3d8486e278374c3b1d746b70bc2604f3bacfab05", + "blockNumber": "0x366", + "blockTimestamp": "0x6a5dfaf2", + "transactionHash": "0x6c4a1e8391bc118a82dfa7c7b662b38329f7468f67b14e980dd0c3b87b990974", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6da590ecdc6dffebffcc312e3d8486e278374c3b1d746b70bc2604f3bacfab05", + "blockNumber": "0x366", + "blockTimestamp": "0x6a5dfaf2", + "transactionHash": "0x6c4a1e8391bc118a82dfa7c7b662b38329f7468f67b14e980dd0c3b87b990974", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x15797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d848dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e", + "blockHash": "0x9c59d8b249d5ae83eb168b06c78785a65adbbe30cd3fc47b713193758b14f13f", + "blockNumber": "0x367", + "blockTimestamp": "0x6a5dfaf3", + "transactionHash": "0xa7ec78b15da0d9a9d2117e133405b1aef7363f3ef5055ff1872ee6df3e9c04e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9c59d8b249d5ae83eb168b06c78785a65adbbe30cd3fc47b713193758b14f13f", + "blockNumber": "0x367", + "blockTimestamp": "0x6a5dfaf3", + "transactionHash": "0xa7ec78b15da0d9a9d2117e133405b1aef7363f3ef5055ff1872ee6df3e9c04e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5a", + "blockHash": "0x2bcf8764252ad777d824950ec9949dd19e7edee3a30a23e27191243f42f4f39a", + "blockNumber": "0x369", + "blockTimestamp": "0x6a5dfaf3", + "transactionHash": "0x9eb8d0fa2d607eed180c4f81c5da1d4930ff1f6eb4a8f17ee29a08fb64eb2fca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2bcf8764252ad777d824950ec9949dd19e7edee3a30a23e27191243f42f4f39a", + "blockNumber": "0x369", + "blockTimestamp": "0x6a5dfaf3", + "transactionHash": "0x9eb8d0fa2d607eed180c4f81c5da1d4930ff1f6eb4a8f17ee29a08fb64eb2fca", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5ad4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2", + "blockHash": "0x49f56c7f0ad3b3aa723a8aaa41a2ebfa90bac6ec2863b259ec2413a7bc87e705", + "blockNumber": "0x36a", + "blockTimestamp": "0x6a5dfaf4", + "transactionHash": "0xed99b87a8797d6ae2bcfa35629103c6eabb95a230df915bab3da9b614301ef0e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x49f56c7f0ad3b3aa723a8aaa41a2ebfa90bac6ec2863b259ec2413a7bc87e705", + "blockNumber": "0x36a", + "blockTimestamp": "0x6a5dfaf4", + "transactionHash": "0xed99b87a8797d6ae2bcfa35629103c6eabb95a230df915bab3da9b614301ef0e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2b262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203", + "blockHash": "0xbae16e67b6cbe68f4032f25ff07e6db26c520364e955925493191ed52db7e791", + "blockNumber": "0x36c", + "blockTimestamp": "0x6a5dfaf5", + "transactionHash": "0x7b200f3a013262806bcff152a680bd6299e1dd2064426453394027e9129506fa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbae16e67b6cbe68f4032f25ff07e6db26c520364e955925493191ed52db7e791", + "blockNumber": "0x36c", + "blockTimestamp": "0x6a5dfaf5", + "transactionHash": "0x7b200f3a013262806bcff152a680bd6299e1dd2064426453394027e9129506fa", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203dbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd87058277565", + "blockHash": "0xbeb7cc19ff254a83cec1b0d62111db1b85ab9fc32e5e701d07757c6e8a1f9f2c", + "blockNumber": "0x36d", + "blockTimestamp": "0x6a5dfaf5", + "transactionHash": "0x881d72d9c825bcd2fc5cccfc5430708831048fef4987e7889079dba182e0177e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbeb7cc19ff254a83cec1b0d62111db1b85ab9fc32e5e701d07757c6e8a1f9f2c", + "blockNumber": "0x36d", + "blockTimestamp": "0x6a5dfaf5", + "transactionHash": "0x881d72d9c825bcd2fc5cccfc5430708831048fef4987e7889079dba182e0177e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd870582775656564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a", + "blockHash": "0xf16600f87e39be8b81a1de07b1c36bbb8e1b3885cb8cdf4c46eb7f26f280056f", + "blockNumber": "0x36f", + "blockTimestamp": "0x6a5dfaf6", + "transactionHash": "0x7410ed544cafca56bf930c0075d6722d2d05aebff5b721706553583e93cc5369", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf16600f87e39be8b81a1de07b1c36bbb8e1b3885cb8cdf4c46eb7f26f280056f", + "blockNumber": "0x36f", + "blockTimestamp": "0x6a5dfaf6", + "transactionHash": "0x7410ed544cafca56bf930c0075d6722d2d05aebff5b721706553583e93cc5369", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cab", + "blockHash": "0x8560c7d8a0c5ba3c96f694d443dbc6ef0b00a0e6b923342f6c8f43558fdc6f34", + "blockNumber": "0x370", + "blockTimestamp": "0x6a5dfaf7", + "transactionHash": "0x920d74c9bec663960c3ef5b984db74b537252442b663aeb92b50621a590aa001", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8560c7d8a0c5ba3c96f694d443dbc6ef0b00a0e6b923342f6c8f43558fdc6f34", + "blockNumber": "0x370", + "blockTimestamp": "0x6a5dfaf7", + "transactionHash": "0x920d74c9bec663960c3ef5b984db74b537252442b663aeb92b50621a590aa001", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cabe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730", + "blockHash": "0x6f97157e78109eca32a4c7e97b652b82cae0efa77108e6fe28a50bd8b24810e1", + "blockNumber": "0x372", + "blockTimestamp": "0x6a5dfaf7", + "transactionHash": "0x0abaa8d60ba12ac8ac8f56152c3d90a12af13a38deb3a0a608eb8eb934ef2338", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6f97157e78109eca32a4c7e97b652b82cae0efa77108e6fe28a50bd8b24810e1", + "blockNumber": "0x372", + "blockTimestamp": "0x6a5dfaf7", + "transactionHash": "0x0abaa8d60ba12ac8ac8f56152c3d90a12af13a38deb3a0a608eb8eb934ef2338", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3", + "blockHash": "0x99785c96d79a05317c5e27d07ed00cca695dfa6763117d2c3cdb05a8abe5ed9e", + "blockNumber": "0x373", + "blockTimestamp": "0x6a5dfaf8", + "transactionHash": "0xa6eb97ec6be36c9cee1e8d44eebe93793d9c05d7f41c95454b844584d4bcef2e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x99785c96d79a05317c5e27d07ed00cca695dfa6763117d2c3cdb05a8abe5ed9e", + "blockNumber": "0x373", + "blockTimestamp": "0x6a5dfaf8", + "transactionHash": "0xa6eb97ec6be36c9cee1e8d44eebe93793d9c05d7f41c95454b844584d4bcef2e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3cb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882", + "blockHash": "0xad20a326613ed6897c4f303d32e41bf6eecf5a18ee38e8958732ef490ac78245", + "blockNumber": "0x375", + "blockTimestamp": "0x6a5dfaf8", + "transactionHash": "0x3d87b699268ceded7ddaa08a2fa51ecc97ae49b4b5e8277252d9a5e71ec8c2bb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xad20a326613ed6897c4f303d32e41bf6eecf5a18ee38e8958732ef490ac78245", + "blockNumber": "0x375", + "blockTimestamp": "0x6a5dfaf8", + "transactionHash": "0x3d87b699268ceded7ddaa08a2fa51ecc97ae49b4b5e8277252d9a5e71ec8c2bb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882c962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c", + "blockHash": "0x4cad9904ca704ceafd1a44f13f836f04ee6d229ddf8b2e085b95e18e1e77c574", + "blockNumber": "0x376", + "blockTimestamp": "0x6a5dfaf9", + "transactionHash": "0x3f4c747746c2e14872f7391aff3a3c987676c3c5e298926494ca979abf85b611", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4cad9904ca704ceafd1a44f13f836f04ee6d229ddf8b2e085b95e18e1e77c574", + "blockNumber": "0x376", + "blockTimestamp": "0x6a5dfaf9", + "transactionHash": "0x3f4c747746c2e14872f7391aff3a3c987676c3c5e298926494ca979abf85b611", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693", + "blockHash": "0xaeacedb476300207c9d1f6779bc31cf2dc3f71e8e76f5bfdb85e8883b2e6cc46", + "blockNumber": "0x378", + "blockTimestamp": "0x6a5dfafa", + "transactionHash": "0xf507918393cc0b115177d1bbff0ce005ac4a7c19f23ca7cec1502f3fd10424b2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xaeacedb476300207c9d1f6779bc31cf2dc3f71e8e76f5bfdb85e8883b2e6cc46", + "blockNumber": "0x378", + "blockTimestamp": "0x6a5dfafa", + "transactionHash": "0xf507918393cc0b115177d1bbff0ce005ac4a7c19f23ca7cec1502f3fd10424b2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693a0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac3", + "blockHash": "0x699517c6d4232fbf16d074ad4fb861ecc6d9297bdb68efdf2912519d923fe937", + "blockNumber": "0x379", + "blockTimestamp": "0x6a5dfafa", + "transactionHash": "0x6c69c83f3b863b9ee50b35e9030fefb4ef08d3cdce940701fec632a5ac34fbfd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x699517c6d4232fbf16d074ad4fb861ecc6d9297bdb68efdf2912519d923fe937", + "blockNumber": "0x379", + "blockTimestamp": "0x6a5dfafa", + "transactionHash": "0x6c69c83f3b863b9ee50b35e9030fefb4ef08d3cdce940701fec632a5ac34fbfd", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac31138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37f", + "blockHash": "0x7ce77f6d482ac85d85804b0a463c1891cf54cebf7d4e48414ec6ebb4ee937741", + "blockNumber": "0x37b", + "blockTimestamp": "0x6a5dfafb", + "transactionHash": "0x4aba81c322a2e985255f066520ffee2f88e3a6e5de7dab320b3ef84158ad1ca4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7ce77f6d482ac85d85804b0a463c1891cf54cebf7d4e48414ec6ebb4ee937741", + "blockNumber": "0x37b", + "blockTimestamp": "0x6a5dfafb", + "transactionHash": "0x4aba81c322a2e985255f066520ffee2f88e3a6e5de7dab320b3ef84158ad1ca4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37fdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653ab", + "blockHash": "0xaa471a2180c854cb2ae6490090d6eee608c39ced901fa289aa43f69dbd11e982", + "blockNumber": "0x37c", + "blockTimestamp": "0x6a5dfafc", + "transactionHash": "0xb73f0a35f99c7f2b932b5c3e1adc2fb2dffe1af7d25f49c474518074e36076ac", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xaa471a2180c854cb2ae6490090d6eee608c39ced901fa289aa43f69dbd11e982", + "blockNumber": "0x37c", + "blockTimestamp": "0x6a5dfafc", + "transactionHash": "0xb73f0a35f99c7f2b932b5c3e1adc2fb2dffe1af7d25f49c474518074e36076ac", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653abc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba633", + "blockHash": "0xafe5e501e4c1776a688ec08c4e0231097ef130e49abf48bba44070849ed315d2", + "blockNumber": "0x37e", + "blockTimestamp": "0x6a5dfafc", + "transactionHash": "0x50c715706d3daf739b4fffa7a32c2de6adadb5ae900918566b17cfec5ad3cc4b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xafe5e501e4c1776a688ec08c4e0231097ef130e49abf48bba44070849ed315d2", + "blockNumber": "0x37e", + "blockTimestamp": "0x6a5dfafc", + "transactionHash": "0x50c715706d3daf739b4fffa7a32c2de6adadb5ae900918566b17cfec5ad3cc4b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba6338ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d1790", + "blockHash": "0xfcd695bb5034e95ecb904ccd7f06bef4fa3999174753bb6d56838d0dc2f23728", + "blockNumber": "0x37f", + "blockTimestamp": "0x6a5dfafd", + "transactionHash": "0x5cfe831a72199e4cf4620d80b872b5608243fd375295740e178d283f8927f154", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfcd695bb5034e95ecb904ccd7f06bef4fa3999174753bb6d56838d0dc2f23728", + "blockNumber": "0x37f", + "blockTimestamp": "0x6a5dfafd", + "transactionHash": "0x5cfe831a72199e4cf4620d80b872b5608243fd375295740e178d283f8927f154", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d179002686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca", + "blockHash": "0x0dbe770b2127b7bbb53a9820c963513eb9bf49ee1ca35a8a467682ba823b937f", + "blockNumber": "0x381", + "blockTimestamp": "0x6a5dfafd", + "transactionHash": "0x89b8d1b44c59e2392c97e7ec45b8fa6bec7b17209cb8078bd4d9f8e7413578ba", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0dbe770b2127b7bbb53a9820c963513eb9bf49ee1ca35a8a467682ba823b937f", + "blockNumber": "0x381", + "blockTimestamp": "0x6a5dfafd", + "transactionHash": "0x89b8d1b44c59e2392c97e7ec45b8fa6bec7b17209cb8078bd4d9f8e7413578ba", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x02686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f36331", + "blockHash": "0x00c5128d594f12f02e676f3fecb8237f60f7023c81a5088b810620441f76f5de", + "blockNumber": "0x382", + "blockTimestamp": "0x6a5dfafe", + "transactionHash": "0x96ecd3f42771a40ee205af83080a24548ac3de7f2464fd5bc98d256c883c5bb0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x00c5128d594f12f02e676f3fecb8237f60f7023c81a5088b810620441f76f5de", + "blockNumber": "0x382", + "blockTimestamp": "0x6a5dfafe", + "transactionHash": "0x96ecd3f42771a40ee205af83080a24548ac3de7f2464fd5bc98d256c883c5bb0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f3633137a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f1", + "blockHash": "0x0aa4f02a7491eff7aa959f194cb6b539d1221e4417202bd586476d763849db35", + "blockNumber": "0x384", + "blockTimestamp": "0x6a5dfafe", + "transactionHash": "0x0705d52a09db3039536531804cf5ad7b958a365760c43940816e8e7fa5d4fd1e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0aa4f02a7491eff7aa959f194cb6b539d1221e4417202bd586476d763849db35", + "blockNumber": "0x384", + "blockTimestamp": "0x6a5dfafe", + "transactionHash": "0x0705d52a09db3039536531804cf5ad7b958a365760c43940816e8e7fa5d4fd1e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x37a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f11fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7", + "blockHash": "0x59d54201606dde2ffc328cb2d6abd0d629bb6735a3229f032c3a0cd85d6b058f", + "blockNumber": "0x385", + "blockTimestamp": "0x6a5dfaff", + "transactionHash": "0x61df9f32f089725958804b7edc5c2c0c624b24e628335d4b4e2624f68c346803", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x59d54201606dde2ffc328cb2d6abd0d629bb6735a3229f032c3a0cd85d6b058f", + "blockNumber": "0x385", + "blockTimestamp": "0x6a5dfaff", + "transactionHash": "0x61df9f32f089725958804b7edc5c2c0c624b24e628335d4b4e2624f68c346803", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7cbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689", + "blockHash": "0xdf744ca2618b47a4453649db2e3d9784767416fadb02cf680011eb943ea93edc", + "blockNumber": "0x387", + "blockTimestamp": "0x6a5dfb00", + "transactionHash": "0x9c996284d054f5bbc90ebc6e4abb97ddec7cc36d89176160010ebb3b8d0c125f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdf744ca2618b47a4453649db2e3d9784767416fadb02cf680011eb943ea93edc", + "blockNumber": "0x387", + "blockTimestamp": "0x6a5dfb00", + "transactionHash": "0x9c996284d054f5bbc90ebc6e4abb97ddec7cc36d89176160010ebb3b8d0c125f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f6896125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f183", + "blockHash": "0xc18d2e7dfcb42161333960a56719ff05a1100f6b4783aadce503e3c3018c8431", + "blockNumber": "0x388", + "blockTimestamp": "0x6a5dfb00", + "transactionHash": "0x9872bf5f9e8ff0515aa34e0851b8da14f4f67e57ec3bec599c3e0716ab3b20b5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc18d2e7dfcb42161333960a56719ff05a1100f6b4783aadce503e3c3018c8431", + "blockNumber": "0x388", + "blockTimestamp": "0x6a5dfb00", + "transactionHash": "0x9872bf5f9e8ff0515aa34e0851b8da14f4f67e57ec3bec599c3e0716ab3b20b5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f18303e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d", + "blockHash": "0x1d1240c2fe77ce0737a6dcaccd6c2729bbf72956d15915b1a18e43a3a166ccad", + "blockNumber": "0x38a", + "blockTimestamp": "0x6a5dfb01", + "transactionHash": "0x56455ec68dda5e28f57670f76ded76dae7a81651a3d789aeb64c6c416ef93998", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1d1240c2fe77ce0737a6dcaccd6c2729bbf72956d15915b1a18e43a3a166ccad", + "blockNumber": "0x38a", + "blockTimestamp": "0x6a5dfb01", + "transactionHash": "0x56455ec68dda5e28f57670f76ded76dae7a81651a3d789aeb64c6c416ef93998", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x03e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a", + "blockHash": "0x2ceab42068b7fa503f2ecc0193903da455ce1475c6c06ab840c1e7b019078278", + "blockNumber": "0x38b", + "blockTimestamp": "0x6a5dfb02", + "transactionHash": "0xbb0af31a1a713d6e19e8450ac747d21c50cb0a02c4e7bef325c445ec56c6519b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2ceab42068b7fa503f2ecc0193903da455ce1475c6c06ab840c1e7b019078278", + "blockNumber": "0x38b", + "blockTimestamp": "0x6a5dfb02", + "transactionHash": "0xbb0af31a1a713d6e19e8450ac747d21c50cb0a02c4e7bef325c445ec56c6519b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f88", + "blockHash": "0xfc779d239043e4767c95cc9e6466f9be3b0191dda2023c54dc71126b62b51a04", + "blockNumber": "0x38d", + "blockTimestamp": "0x6a5dfb02", + "transactionHash": "0x5249f9491417a4a33c6ec122131019fb958770ac39d81b29fb1f4f6a797a269e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfc779d239043e4767c95cc9e6466f9be3b0191dda2023c54dc71126b62b51a04", + "blockNumber": "0x38d", + "blockTimestamp": "0x6a5dfb02", + "transactionHash": "0x5249f9491417a4a33c6ec122131019fb958770ac39d81b29fb1f4f6a797a269e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f885a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd8925", + "blockHash": "0xfdcd42732dbb464444ee22f075e73a54c96bd9e53cc56aac695a3149514e4523", + "blockNumber": "0x38e", + "blockTimestamp": "0x6a5dfb03", + "transactionHash": "0x5578fc4e524c2c007107c1fc223e32c16fcd5ff2df16c69dbcf1070a2b5eeef5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfdcd42732dbb464444ee22f075e73a54c96bd9e53cc56aac695a3149514e4523", + "blockNumber": "0x38e", + "blockTimestamp": "0x6a5dfb03", + "transactionHash": "0x5578fc4e524c2c007107c1fc223e32c16fcd5ff2df16c69dbcf1070a2b5eeef5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x5a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd89254b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd77", + "blockHash": "0xfebd74973ffc714fdabe74c37b317ea8eecc9a3cc8c4c284b37aff8f1e29e667", + "blockNumber": "0x390", + "blockTimestamp": "0x6a5dfb03", + "transactionHash": "0xf440ffea4ec2f67e3af4ff1a59010ac400b7e1705e80271e64ed5ac7a469aba5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfebd74973ffc714fdabe74c37b317ea8eecc9a3cc8c4c284b37aff8f1e29e667", + "blockNumber": "0x390", + "blockTimestamp": "0x6a5dfb03", + "transactionHash": "0xf440ffea4ec2f67e3af4ff1a59010ac400b7e1705e80271e64ed5ac7a469aba5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd7771c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98b", + "blockHash": "0xac6abdaa9875d8e65a06542e2ee2cb27da9e1e400787e2ec024f6e49dcf03500", + "blockNumber": "0x391", + "blockTimestamp": "0x6a5dfb04", + "transactionHash": "0x441f261439901e80b0008abe5d2d38a2adfceea5e30d6378c3ed8bfb9b8fa8ec", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xac6abdaa9875d8e65a06542e2ee2cb27da9e1e400787e2ec024f6e49dcf03500", + "blockNumber": "0x391", + "blockTimestamp": "0x6a5dfb04", + "transactionHash": "0x441f261439901e80b0008abe5d2d38a2adfceea5e30d6378c3ed8bfb9b8fa8ec", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x71c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98bcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402", + "blockHash": "0x9e3495306eaa3fe73a0aab7f9a812fa2505ac708099a75be35b7d28c116861ce", + "blockNumber": "0x393", + "blockTimestamp": "0x6a5dfb04", + "transactionHash": "0xa6e4cf45b705420035d039bd7a0ebb2be163f846ba2f772ee0e121783996b54a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9e3495306eaa3fe73a0aab7f9a812fa2505ac708099a75be35b7d28c116861ce", + "blockNumber": "0x393", + "blockTimestamp": "0x6a5dfb04", + "transactionHash": "0xa6e4cf45b705420035d039bd7a0ebb2be163f846ba2f772ee0e121783996b54a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402d2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b", + "blockHash": "0x1f3fdfb8e29ed7c506afcf6eb4456caa8049cf52516ce6eff0d661e128363695", + "blockNumber": "0x394", + "blockTimestamp": "0x6a5dfb05", + "transactionHash": "0xc1096ca24bb16643a5d6c69aa882b21c8d4817036f7df6054927434a2a441939", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1f3fdfb8e29ed7c506afcf6eb4456caa8049cf52516ce6eff0d661e128363695", + "blockNumber": "0x394", + "blockTimestamp": "0x6a5dfb05", + "transactionHash": "0xc1096ca24bb16643a5d6c69aa882b21c8d4817036f7df6054927434a2a441939", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x9fc5fd18006ba09f8df6238c12681079bd5501a3a7a3ff236de17236797813ef", + "blockNumber": "0x396", + "blockTimestamp": "0x6a5dfb06", + "transactionHash": "0xdb56f014a8a937082806c688123008b3f98815b57c0714240a4b0011627d5da2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9fc5fd18006ba09f8df6238c12681079bd5501a3a7a3ff236de17236797813ef", + "blockNumber": "0x396", + "blockTimestamp": "0x6a5dfb06", + "transactionHash": "0xdb56f014a8a937082806c688123008b3f98815b57c0714240a4b0011627d5da2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x000000000000000000000000d89b879abf354b3912f764fbd597c51d6e33340d" + ], + "data": "0x", + "blockHash": "0x52acabcd38138b5e35b1d73d75f06dfb9cd0666570965c4099ccc1e9bcff7619", + "blockNumber": "0x397", + "blockTimestamp": "0x6a5dfb07", + "transactionHash": "0xccc0b1eecae4035f3b47c8c1690d3f9041c150f2fa5f75ebdc2591901f51c76a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000024e86000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x52acabcd38138b5e35b1d73d75f06dfb9cd0666570965c4099ccc1e9bcff7619", + "blockNumber": "0x397", + "blockTimestamp": "0x6a5dfb07", + "transactionHash": "0xccc0b1eecae4035f3b47c8c1690d3f9041c150f2fa5f75ebdc2591901f51c76a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x6dd387a67968e6991d193ed3c065d438be85e7bd21e5113cbd12b094b4265dc001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xd8865393f470fae84d04cacbff00eca4e3a609904f4a7ccc44791a2c07f8257f", + "blockNumber": "0x399", + "blockTimestamp": "0x6a5dfb07", + "transactionHash": "0x048e923fcf4747c307807e341fbcd6c39afa7d73ef8b672bd3e32052e88f81d7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0x64402689273954a4c2477ffa8737d08da78862e1313e2d7ee24550ec62cf02640000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7e133bdca1c91205ac31592693421ecff92377806f584d66e7c0e93ca335485c", + "blockNumber": "0x39a", + "blockTimestamp": "0x6a5dfb08", + "transactionHash": "0x3b1b0a6ecb86fc56662383aa21f3bd5de30db880d348545ef1b9e7668b8d4356", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21", + "0x6dd387a67968e6991d193ed3c065d438be85e7bd21e5113cbd12b094b4265dc0", + "0x64402689273954a4c2477ffa8737d08da78862e1313e2d7ee24550ec62cf0264" + ], + "data": "0x66f2daef78ab30605fff578098b8d0cec0eaf1ef579fa5dc3ce89310ff23e458", + "blockHash": "0x7e133bdca1c91205ac31592693421ecff92377806f584d66e7c0e93ca335485c", + "blockNumber": "0x39a", + "blockTimestamp": "0x6a5dfb08", + "transactionHash": "0x3b1b0a6ecb86fc56662383aa21f3bd5de30db880d348545ef1b9e7668b8d4356", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x66f2daef78ab30605fff578098b8d0cec0eaf1ef579fa5dc3ce89310ff23e458b783c8f943f4e2332b14a1da99949a4f8b91e566ad7da74104c26ac25acf4928", + "blockHash": "0xcf727091b2af9765d64b04a5c4647adf85ba944f74f30b2a3244a48434333724", + "blockNumber": "0x39c", + "blockTimestamp": "0x6a5dfb08", + "transactionHash": "0xfb7474e81ce225d708fcb5a4855ea75eb3a4c470575bf9a26576f7af284f446a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcf727091b2af9765d64b04a5c4647adf85ba944f74f30b2a3244a48434333724", + "blockNumber": "0x39c", + "blockTimestamp": "0x6a5dfb08", + "transactionHash": "0xfb7474e81ce225d708fcb5a4855ea75eb3a4c470575bf9a26576f7af284f446a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0xb783c8f943f4e2332b14a1da99949a4f8b91e566ad7da74104c26ac25acf4928d0dd972c3d35bee7ce7859966d5c0bdd941bcc3a2905b457b5f02605651f7545", + "blockHash": "0xfba302ba04d6e3eaa027724b15216bf0a7d242e011437b262c917265bd8bcdcd", + "blockNumber": "0x39d", + "blockTimestamp": "0x6a5dfb09", + "transactionHash": "0x0ea56e99269ab3117064904e9cd1c14fe84f14fda6f7e7eab29b34bef586b209", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfba302ba04d6e3eaa027724b15216bf0a7d242e011437b262c917265bd8bcdcd", + "blockNumber": "0x39d", + "blockTimestamp": "0x6a5dfb09", + "transactionHash": "0x0ea56e99269ab3117064904e9cd1c14fe84f14fda6f7e7eab29b34bef586b209", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0xd0dd972c3d35bee7ce7859966d5c0bdd941bcc3a2905b457b5f02605651f75451ba9baf2f3396f317ba0e31243fa250fd03bea9f39c63f1f3239f43f4da79b7b", + "blockHash": "0x648580cd62f43cdcb9a37e20eb40bcd863b58f5d49ac6441ea2ca0287e98bff8", + "blockNumber": "0x39f", + "blockTimestamp": "0x6a5dfb0a", + "transactionHash": "0xe1103097dae339173b584aa5c5fd8d93ec1490acdd6f1979ecfd161319460be6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x648580cd62f43cdcb9a37e20eb40bcd863b58f5d49ac6441ea2ca0287e98bff8", + "blockNumber": "0x39f", + "blockTimestamp": "0x6a5dfb0a", + "transactionHash": "0xe1103097dae339173b584aa5c5fd8d93ec1490acdd6f1979ecfd161319460be6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x1ba9baf2f3396f317ba0e31243fa250fd03bea9f39c63f1f3239f43f4da79b7b4847005cbdeb3f3d136361486804ffb97fb06b9b95403be264b8ad896c715670", + "blockHash": "0x350827ee81e7cab4920aeff6b4a9cb37e81b7a51f88d22bdeb528620ce446332", + "blockNumber": "0x3a0", + "blockTimestamp": "0x6a5dfb0b", + "transactionHash": "0xd013ffd4c6781f9d91751e062bdae397bbb7806b1439edf48c5c7693e11ffe15", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x350827ee81e7cab4920aeff6b4a9cb37e81b7a51f88d22bdeb528620ce446332", + "blockNumber": "0x3a0", + "blockTimestamp": "0x6a5dfb0b", + "transactionHash": "0xd013ffd4c6781f9d91751e062bdae397bbb7806b1439edf48c5c7693e11ffe15", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x4847005cbdeb3f3d136361486804ffb97fb06b9b95403be264b8ad896c715670db3a89ce2b7c30d96b73eff98ec54ca12d38fb937931660ab83db9f49612773d", + "blockHash": "0xeda3dfb969e3576e68992f4366f98a72787c0105923517a3564dd974cc9e57b9", + "blockNumber": "0x3a2", + "blockTimestamp": "0x6a5dfb0b", + "transactionHash": "0xdecc2c031d39b27704e1209eca40e0e47f1247a70c5732571bc82ad9b253427b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xeda3dfb969e3576e68992f4366f98a72787c0105923517a3564dd974cc9e57b9", + "blockNumber": "0x3a2", + "blockTimestamp": "0x6a5dfb0b", + "transactionHash": "0xdecc2c031d39b27704e1209eca40e0e47f1247a70c5732571bc82ad9b253427b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0xdb3a89ce2b7c30d96b73eff98ec54ca12d38fb937931660ab83db9f49612773d917e429266787b60bce31a7756f7c49bf477167483e1d5014d21885dc28356f2", + "blockHash": "0xab280a6d77518ec396c863362e8868ff406fedd73f00e96290f2d6d6738572e2", + "blockNumber": "0x3a3", + "blockTimestamp": "0x6a5dfb0c", + "transactionHash": "0x2f5931e4ccc317004d816e369c45dce9d4f63125777367da421956b0238d4f69", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xab280a6d77518ec396c863362e8868ff406fedd73f00e96290f2d6d6738572e2", + "blockNumber": "0x3a3", + "blockTimestamp": "0x6a5dfb0c", + "transactionHash": "0x2f5931e4ccc317004d816e369c45dce9d4f63125777367da421956b0238d4f69", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x917e429266787b60bce31a7756f7c49bf477167483e1d5014d21885dc28356f218bdeab3d84c77cccf394f97fcc7909940074b5b2fdf0eb85b0b3b3ed8a2509f", + "blockHash": "0x29b3764d347be6f809e381a5ed93cb84b0ef00747480c70dbe030fe0037d2342", + "blockNumber": "0x3a5", + "blockTimestamp": "0x6a5dfb0c", + "transactionHash": "0x089679dc5a77b1eef12c6ba87408ef60adeabbfb8ad2a6cc5851ad97ce80cb46", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x29b3764d347be6f809e381a5ed93cb84b0ef00747480c70dbe030fe0037d2342", + "blockNumber": "0x3a5", + "blockTimestamp": "0x6a5dfb0c", + "transactionHash": "0x089679dc5a77b1eef12c6ba87408ef60adeabbfb8ad2a6cc5851ad97ce80cb46", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x18bdeab3d84c77cccf394f97fcc7909940074b5b2fdf0eb85b0b3b3ed8a2509f6d96901cbdeba6d3497a11d3de502bda881b1c683fadea95a7dd6a11bf6011dc", + "blockHash": "0xdec2155a6e16b46da43016dcacfe40c9da046264ab43c79245f715a4f08eac84", + "blockNumber": "0x3a6", + "blockTimestamp": "0x6a5dfb0e", + "transactionHash": "0x0f6ab004c46e5a0a94f56105d6a086f3924544c5d8ab458c632c351c4d5cd052", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdec2155a6e16b46da43016dcacfe40c9da046264ab43c79245f715a4f08eac84", + "blockNumber": "0x3a6", + "blockTimestamp": "0x6a5dfb0e", + "transactionHash": "0x0f6ab004c46e5a0a94f56105d6a086f3924544c5d8ab458c632c351c4d5cd052", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x6d96901cbdeba6d3497a11d3de502bda881b1c683fadea95a7dd6a11bf6011dc9dcd37e69ee4ff3f230000a0bdfc2a7a6b2ed51a1a2dbee075631d1d51d790a6", + "blockHash": "0x9453ad4477851a7389714f15d2768c8f1d5115a75bfe7f9c2baec2c1135d86eb", + "blockNumber": "0x3a8", + "blockTimestamp": "0x6a5dfb0e", + "transactionHash": "0xbbed15d89efadb8631b65250b311c69d2ed09a3b8e06ffab9462a1a93ba63f2d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9453ad4477851a7389714f15d2768c8f1d5115a75bfe7f9c2baec2c1135d86eb", + "blockNumber": "0x3a8", + "blockTimestamp": "0x6a5dfb0e", + "transactionHash": "0xbbed15d89efadb8631b65250b311c69d2ed09a3b8e06ffab9462a1a93ba63f2d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x9dcd37e69ee4ff3f230000a0bdfc2a7a6b2ed51a1a2dbee075631d1d51d790a6554e27d2bb7cd607bbaa9afb0aad16d9602f05320b366ab42a8ada87261ebddd", + "blockHash": "0xe380a628f502cedad336b6f7c33e9fd56a1263ce8e8bbb3aad5eeb61024076f4", + "blockNumber": "0x3a9", + "blockTimestamp": "0x6a5dfb0f", + "transactionHash": "0xab7b8c22989d0098ee656cbfc33227ae9245a3b5c6230e459546a8e2944f8df5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe380a628f502cedad336b6f7c33e9fd56a1263ce8e8bbb3aad5eeb61024076f4", + "blockNumber": "0x3a9", + "blockTimestamp": "0x6a5dfb0f", + "transactionHash": "0xab7b8c22989d0098ee656cbfc33227ae9245a3b5c6230e459546a8e2944f8df5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x554e27d2bb7cd607bbaa9afb0aad16d9602f05320b366ab42a8ada87261ebddd4c7c7e44a89d7ba681ef0654547a0770509b52ebb1ac7edbc4b087e518f560ff", + "blockHash": "0x80892617d8f9f1c9dac8f1646933e247ed2e03a6dcd000f41c91a5c88d0ff067", + "blockNumber": "0x3ab", + "blockTimestamp": "0x6a5dfb0f", + "transactionHash": "0x41db91447d944fdedd91d01779a144c9f6e741e8e053838eefeeae52910d89c4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x80892617d8f9f1c9dac8f1646933e247ed2e03a6dcd000f41c91a5c88d0ff067", + "blockNumber": "0x3ab", + "blockTimestamp": "0x6a5dfb0f", + "transactionHash": "0x41db91447d944fdedd91d01779a144c9f6e741e8e053838eefeeae52910d89c4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x4c7c7e44a89d7ba681ef0654547a0770509b52ebb1ac7edbc4b087e518f560ffc4dbd4c76566c5c9531cff9eb8789e0c6c1fbc3b8400a6f5616a1dafd905080a", + "blockHash": "0x18b630a96893ff620395174a642da28ff5a8780eeb024081e7fbc393a0bb252c", + "blockNumber": "0x3ac", + "blockTimestamp": "0x6a5dfb11", + "transactionHash": "0xc1f2db42a37c699f38da69c4ce1e04c1863299fbcde7accb36b134e129dfb31e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x18b630a96893ff620395174a642da28ff5a8780eeb024081e7fbc393a0bb252c", + "blockNumber": "0x3ac", + "blockTimestamp": "0x6a5dfb11", + "transactionHash": "0xc1f2db42a37c699f38da69c4ce1e04c1863299fbcde7accb36b134e129dfb31e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0xc4dbd4c76566c5c9531cff9eb8789e0c6c1fbc3b8400a6f5616a1dafd905080a4fc3288e7ef5412571c21f01a284272292930c832152ee5aa936b53b5b1008c0", + "blockHash": "0x728b85207ac7e13220bb57877d8d8248c8f988b775601ef9ebca9b3baaba3f3e", + "blockNumber": "0x3ae", + "blockTimestamp": "0x6a5dfb11", + "transactionHash": "0xbc93501aad99971a4dcb888281e63b5b95c99f8c7889b8e8d819c728841dbbba", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x728b85207ac7e13220bb57877d8d8248c8f988b775601ef9ebca9b3baaba3f3e", + "blockNumber": "0x3ae", + "blockTimestamp": "0x6a5dfb11", + "transactionHash": "0xbc93501aad99971a4dcb888281e63b5b95c99f8c7889b8e8d819c728841dbbba", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x4fc3288e7ef5412571c21f01a284272292930c832152ee5aa936b53b5b1008c0409b261a2362fb7858ddb2e420453176b32b916137b692e946fbcbb70372c1bf", + "blockHash": "0x0c35ec355cb6b741cfa55c61d07203e0543d0bd63c38a609a3752ec6cda05af3", + "blockNumber": "0x3af", + "blockTimestamp": "0x6a5dfb12", + "transactionHash": "0x7f3469478e3ed99e5d5368afec9cd7ee23c808fde0dcaca239ba07fa80994eae", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0c35ec355cb6b741cfa55c61d07203e0543d0bd63c38a609a3752ec6cda05af3", + "blockNumber": "0x3af", + "blockTimestamp": "0x6a5dfb12", + "transactionHash": "0x7f3469478e3ed99e5d5368afec9cd7ee23c808fde0dcaca239ba07fa80994eae", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x409b261a2362fb7858ddb2e420453176b32b916137b692e946fbcbb70372c1bf150af01c653c4ca14ecc9e3a3276af39828296f41a012c6ca04067505155b352", + "blockHash": "0x8e469fae2a2e0fdc4156ef1960d1c98bb14f1e210fdeb849d37455d7fb0a0c64", + "blockNumber": "0x3b1", + "blockTimestamp": "0x6a5dfb12", + "transactionHash": "0xa2979102f7445b0b632d860bd2b4ffb839242f67616c0b6873dc6cbe39676152", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8e469fae2a2e0fdc4156ef1960d1c98bb14f1e210fdeb849d37455d7fb0a0c64", + "blockNumber": "0x3b1", + "blockTimestamp": "0x6a5dfb12", + "transactionHash": "0xa2979102f7445b0b632d860bd2b4ffb839242f67616c0b6873dc6cbe39676152", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21" + ], + "data": "0x150af01c653c4ca14ecc9e3a3276af39828296f41a012c6ca04067505155b352ebfdcac9bdcbef02050df85773ba7d7e4c4acdeb3da820e7782aa319f7a8f047", + "blockHash": "0xc29f64dd996b5a54e313e61b50fa045318b98f628f420e37db4efdf2b736c722", + "blockNumber": "0x3b2", + "blockTimestamp": "0x6a5dfb13", + "transactionHash": "0xfc499c47591964bbcfe167cfece2953bdd6bcc22d2dd7c317bf314363529dc15", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc29f64dd996b5a54e313e61b50fa045318b98f628f420e37db4efdf2b736c722", + "blockNumber": "0x3b2", + "blockTimestamp": "0x6a5dfb13", + "transactionHash": "0xfc499c47591964bbcfe167cfece2953bdd6bcc22d2dd7c317bf314363529dc15", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21", + "0x0000000000000000000000006ec4c9d2fd20ce509f40737561156c1091834121" + ], + "data": "0x", + "blockHash": "0x112a4a92837145a5d12ea38bbec88922ef35626ffdb02ad6480cf7e3745055a7", + "blockNumber": "0x3b4", + "blockTimestamp": "0x6a5dfb14", + "transactionHash": "0xd1b24df0661ecf30892a3b42682b4e14ed7dc23e1a046fb752ca3e7821c5e189", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000027ec3800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x112a4a92837145a5d12ea38bbec88922ef35626ffdb02ad6480cf7e3745055a7", + "blockNumber": "0x3b4", + "blockTimestamp": "0x6a5dfb14", + "transactionHash": "0xd1b24df0661ecf30892a3b42682b4e14ed7dc23e1a046fb752ca3e7821c5e189", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" + ], + "data": "0x249aa34bdcc24fceda18de0c67c9994f5297415a113f39df42349ce4d473b4f60000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa0ffc914d7433f5235ce21bf1d2e5475533266fa05c28f042c139eb0ec9106c3", + "blockNumber": "0x3b5", + "blockTimestamp": "0x6a5dfb17", + "transactionHash": "0xfc4904d4d3212d7ed35a65b0ab74a1f3c09e43c2aae7e7c6b9662ccb21bb7e56", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x7ed6c4201672c24a03dbdb6c867b1922815705bd09039712cc564f19b48183ea26b79a7fa1e73364278cb97caa727a704e5a7371b9ebf6ee011017128a750afc", + "blockHash": "0x7a2950b162495c63c85e9d38262eeb9ecc6dcc80ab1dd63dfc14c45af1aa6154", + "blockNumber": "0x3b7", + "blockTimestamp": "0x6a5dfb18", + "transactionHash": "0x05ad403966700e8c561734026ddebfe6dde38a20cce0041380d280e0bb40da4e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9", + "0x249aa34bdcc24fceda18de0c67c9994f5297415a113f39df42349ce4d473b4f6", + "0x7ed6c4201672c24a03dbdb6c867b1922815705bd09039712cc564f19b48183ea" + ], + "data": "0x923e7a5eb3252151ca896504811480f3890c194eec146abfc58b7e416938e62c", + "blockHash": "0x7a2950b162495c63c85e9d38262eeb9ecc6dcc80ab1dd63dfc14c45af1aa6154", + "blockNumber": "0x3b7", + "blockTimestamp": "0x6a5dfb18", + "transactionHash": "0x05ad403966700e8c561734026ddebfe6dde38a20cce0041380d280e0bb40da4e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x827d83839f233688e408dbf24efd60dce2b97d0b5047041bc2ac8523d0a4accfedb36c3bd03f8615f1248fe385a83aac1bcfb4e17ba9cebd118b52f1a0c10648", + "blockHash": "0x627c5449c161f83e5f1345047ac0091ad4b402066e0cb6e7ba36d7e22d6e1554", + "blockNumber": "0x3b8", + "blockTimestamp": "0x6a5dfb18", + "transactionHash": "0x3845e526a406efd85fa2eddf27ef52bb555b660c1d139301395d9b14ae970890", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x627c5449c161f83e5f1345047ac0091ad4b402066e0cb6e7ba36d7e22d6e1554", + "blockNumber": "0x3b8", + "blockTimestamp": "0x6a5dfb18", + "transactionHash": "0x3845e526a406efd85fa2eddf27ef52bb555b660c1d139301395d9b14ae970890", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x591e2e047eced18a6fe832c2f0e0d5f187c94a42dc06a5f21bc21841479d33fded38c43c72d93d06776e0fbd5bdf38286f0e13dfa7a21669c4addb0f343c8622", + "blockHash": "0x7fff0326f87d9b4e9ba343566e38a269d474978156412aebd26b047a12e15707", + "blockNumber": "0x3ba", + "blockTimestamp": "0x6a5dfb19", + "transactionHash": "0x22ad0adcf35367673f0258752245da955ea5f8c685768a46f723021c711a2611", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7fff0326f87d9b4e9ba343566e38a269d474978156412aebd26b047a12e15707", + "blockNumber": "0x3ba", + "blockTimestamp": "0x6a5dfb19", + "transactionHash": "0x22ad0adcf35367673f0258752245da955ea5f8c685768a46f723021c711a2611", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x8987589e184bea8f9277580961a75227bf69034128990f4170aa65319d315d2e86bf6b7ec44757825726bb643799b34f19229f44cf2fbc381d260e3756169e41", + "blockHash": "0xb6266db8815372bb22f2deb9ece7ed30f8566c7df3c051e5e4cd24a6a07de2df", + "blockNumber": "0x3bb", + "blockTimestamp": "0x6a5dfb1a", + "transactionHash": "0xa66376c139a59e422d6356c1ed2b6659481cfef90e261c2577fd2c2410a324a3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb6266db8815372bb22f2deb9ece7ed30f8566c7df3c051e5e4cd24a6a07de2df", + "blockNumber": "0x3bb", + "blockTimestamp": "0x6a5dfb1a", + "transactionHash": "0xa66376c139a59e422d6356c1ed2b6659481cfef90e261c2577fd2c2410a324a3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xbc047a4523295c2cd9ec82332fda89a2d6b2ff38ebbc4ec743365777433e5402d6be657b2c19fa982e000be50881cc317eed9ba71e81f43f4da736d9d78030fc", + "blockHash": "0xd9d0c6ee93712dff98b9dcdb4dd5f3ccb184f4c796f7fc55fa5b5f3d088e02b7", + "blockNumber": "0x3bd", + "blockTimestamp": "0x6a5dfb1a", + "transactionHash": "0xd79221396da1b47328c259d9c60911f7d6db24a5d7009c78ae45bb5ca2f25daf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd9d0c6ee93712dff98b9dcdb4dd5f3ccb184f4c796f7fc55fa5b5f3d088e02b7", + "blockNumber": "0x3bd", + "blockTimestamp": "0x6a5dfb1a", + "transactionHash": "0xd79221396da1b47328c259d9c60911f7d6db24a5d7009c78ae45bb5ca2f25daf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xff8014802474ec3661c78bcc6bc9db7b1bf85b65851f02ee5ec95e3d17f09949c6c94187815abfcbba5967cc98a59fe46a3c7526baed8a99ab8aca1f437c976f", + "blockHash": "0x86f07e47d5ba7d7d8ca9ec2e12d0cbb44db747d851a19e4af2bafdf1d8de602c", + "blockNumber": "0x3be", + "blockTimestamp": "0x6a5dfb1b", + "transactionHash": "0x572f2389e2623a25ef475d49a95514b9fdd9bf87af685dce9485afc81affb928", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x86f07e47d5ba7d7d8ca9ec2e12d0cbb44db747d851a19e4af2bafdf1d8de602c", + "blockNumber": "0x3be", + "blockTimestamp": "0x6a5dfb1b", + "transactionHash": "0x572f2389e2623a25ef475d49a95514b9fdd9bf87af685dce9485afc81affb928", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xca2363d86165aa731f178553f616788ee0ded33274f877985d7b691333fc0ed07965fc2a4040d14a2e6b8c16098b5093b3be87b2e0db668200bfc5fa78ad2c9e", + "blockHash": "0xcc2c5fef93300b8273482f5b9192fb43cdd39900fbe1efba795bf500ce44b798", + "blockNumber": "0x3c0", + "blockTimestamp": "0x6a5dfb1c", + "transactionHash": "0x714be4fa8290a300db436c9ac301a9d87bb33a77a8dda7da7819f0601ed36977", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcc2c5fef93300b8273482f5b9192fb43cdd39900fbe1efba795bf500ce44b798", + "blockNumber": "0x3c0", + "blockTimestamp": "0x6a5dfb1c", + "transactionHash": "0x714be4fa8290a300db436c9ac301a9d87bb33a77a8dda7da7819f0601ed36977", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x5e16e404b7134a62ff888aa14e30d81c83f708e697c6aa626700ae4952b89a80d0194353eea1fc9a3d8e0b56765f9bbdb70eb4d2d7864081e3eb4404500372bd", + "blockHash": "0x98f3adcf8c2c4ce0d19ced23ede24ccb8b872370230d25a18d944610ac4af867", + "blockNumber": "0x3c1", + "blockTimestamp": "0x6a5dfb1d", + "transactionHash": "0xcfdd9ececc27a3164f749be77b7883aca835ce264a9dbe1d9d2580fca4781406", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x98f3adcf8c2c4ce0d19ced23ede24ccb8b872370230d25a18d944610ac4af867", + "blockNumber": "0x3c1", + "blockTimestamp": "0x6a5dfb1d", + "transactionHash": "0xcfdd9ececc27a3164f749be77b7883aca835ce264a9dbe1d9d2580fca4781406", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xb6206db17535c10b3f114d2573150cf5dc67666e0e777a28528b295dd903ff46a756fff939a2b867439f165952f27692db5a874a2b8e45d2c01b2359779d37a2", + "blockHash": "0x0951d824eed88f3b6c9f8058d9c527f49a5b406fa5c5a832a3942c838352ebbf", + "blockNumber": "0x3c3", + "blockTimestamp": "0x6a5dfb1e", + "transactionHash": "0x39b5699481f8f1a9e0c91960cb71c55a06f6c17d82d46e062cc0d7deb435477d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0951d824eed88f3b6c9f8058d9c527f49a5b406fa5c5a832a3942c838352ebbf", + "blockNumber": "0x3c3", + "blockTimestamp": "0x6a5dfb1e", + "transactionHash": "0x39b5699481f8f1a9e0c91960cb71c55a06f6c17d82d46e062cc0d7deb435477d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xd3e92ee1db7d208c483fe1604b4bee76772c955d55c6ad8db00aa6b1f3b1e2848cea6732d21534055aff8487313a1daefdca042f18ac1d553d5b52ad254105b6", + "blockHash": "0x68c750f45542dfde09900621cd7ee858f188b7a9f66435f0b6327d9af21ebf59", + "blockNumber": "0x3c4", + "blockTimestamp": "0x6a5dfb1f", + "transactionHash": "0xb0c8c6b19f8f68cd638ec3dd9fe03dbdb0ef911b3a1e2046e95ad265421991ce", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x68c750f45542dfde09900621cd7ee858f188b7a9f66435f0b6327d9af21ebf59", + "blockNumber": "0x3c4", + "blockTimestamp": "0x6a5dfb1f", + "transactionHash": "0xb0c8c6b19f8f68cd638ec3dd9fe03dbdb0ef911b3a1e2046e95ad265421991ce", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xce65000fe44832d53803b63eb810883c71293142880ff5ba575e86fdb6821fe21920e825f0d6d5c293cc1949c8979c9b01aca84934de653a8807a2a9cdf5905e", + "blockHash": "0xaf9387f39840c76a89780f4ab34bca8ca2e2735d5754fb3116599e32ee9c214d", + "blockNumber": "0x3c6", + "blockTimestamp": "0x6a5dfb1f", + "transactionHash": "0x936c28656a911100828c9e684f5a3883f9d3f81ed0188be7023c9751b70077a9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xaf9387f39840c76a89780f4ab34bca8ca2e2735d5754fb3116599e32ee9c214d", + "blockNumber": "0x3c6", + "blockTimestamp": "0x6a5dfb1f", + "transactionHash": "0x936c28656a911100828c9e684f5a3883f9d3f81ed0188be7023c9751b70077a9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x65fe454a10546adbfae9327eb2e56a1c36b8f55e483e408306980b223dc98253379d5100bd7f8198a086ca6bd1e661aad5a887cd96d8982b07321f0ef7e6f9f3", + "blockHash": "0xe4dbafcb16d3e98b7b10bd0e28543b0785fae1ae9cb0e73ecc99184dfaac24e0", + "blockNumber": "0x3c7", + "blockTimestamp": "0x6a5dfb20", + "transactionHash": "0x2c8dc9dc6c32a491cc39bec83687feb056e732fbbe520079ee8c015069a2a2e9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe4dbafcb16d3e98b7b10bd0e28543b0785fae1ae9cb0e73ecc99184dfaac24e0", + "blockNumber": "0x3c7", + "blockTimestamp": "0x6a5dfb20", + "transactionHash": "0x2c8dc9dc6c32a491cc39bec83687feb056e732fbbe520079ee8c015069a2a2e9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xf5faa585ae5039223bdb4544318ef36fcffdcaac97e6119b4c2eabaa3452bfc5fb357eb7f1b1ac95b08336c65c75d74453fc287dad3b5d4eaf04cb22ee60769a", + "blockHash": "0x6e0dab12b42d8aca96c3263395fc0fafca3ec087a43566327f867cd7a21f19bf", + "blockNumber": "0x3c9", + "blockTimestamp": "0x6a5dfb21", + "transactionHash": "0xd275dad23e0e4e2051a9da432fb7242af5f3512791fa8db695cd36276faf54d4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6e0dab12b42d8aca96c3263395fc0fafca3ec087a43566327f867cd7a21f19bf", + "blockNumber": "0x3c9", + "blockTimestamp": "0x6a5dfb21", + "transactionHash": "0xd275dad23e0e4e2051a9da432fb7242af5f3512791fa8db695cd36276faf54d4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xb5918b77d44d08e1c2ae2ab922c8c026ddd89c4831d683b35560176fa2d523e9bcd352683002bb872b47e12f972ff5b918443bcd38da265bebec4c73530d027d", + "blockHash": "0xc1daac9d38337835271ba3455a462a0b6b654aff624b009db06fe0a0607263c4", + "blockNumber": "0x3ca", + "blockTimestamp": "0x6a5dfb22", + "transactionHash": "0x67d21059b0958f83047347f9aef2dba0b73a5c91c9ed3e9a3111c07655c0a664", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc1daac9d38337835271ba3455a462a0b6b654aff624b009db06fe0a0607263c4", + "blockNumber": "0x3ca", + "blockTimestamp": "0x6a5dfb22", + "transactionHash": "0x67d21059b0958f83047347f9aef2dba0b73a5c91c9ed3e9a3111c07655c0a664", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x1c0cf37010129d2f400d730bf1910e3f157ea163359b6c2b97047b46c53a56f519bceb513a1186308bc0c70d14c4da86c5038ff49065e2886ba94e60018dec32", + "blockHash": "0x1505583693fe540f626513fa8b7c6263ba9383fed04e19613cfd5c347f32a18c", + "blockNumber": "0x3cc", + "blockTimestamp": "0x6a5dfb22", + "transactionHash": "0x88dff85e22022dbddd8abd38b249ef4edb3325b922e7943777fd00872648a3a8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1505583693fe540f626513fa8b7c6263ba9383fed04e19613cfd5c347f32a18c", + "blockNumber": "0x3cc", + "blockTimestamp": "0x6a5dfb22", + "transactionHash": "0x88dff85e22022dbddd8abd38b249ef4edb3325b922e7943777fd00872648a3a8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x2ff3ef4ce54309f8011a985ec7cc9093b9ee274d64f21e747e1b68bd396a8071cbea5961168c8ee0d62b5643c656e9a52b1ec44f1af155fd7aeb9f1063039938", + "blockHash": "0x4230f89c111514b67c67c06627b1c8aaffa9f42b80e8bb61ec4b740f754f1690", + "blockNumber": "0x3cd", + "blockTimestamp": "0x6a5dfb24", + "transactionHash": "0x2b72c47a3358560ba35acd991d5f77f180410d1cd6397ecb67d6597d179e5865", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4230f89c111514b67c67c06627b1c8aaffa9f42b80e8bb61ec4b740f754f1690", + "blockNumber": "0x3cd", + "blockTimestamp": "0x6a5dfb24", + "transactionHash": "0x2b72c47a3358560ba35acd991d5f77f180410d1cd6397ecb67d6597d179e5865", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x4ee9d5c91d568c4c94cd6c354123f313279219c99bd1e3812ebaa03fd0dc6aecf06e68f61682b8b8fd30edb2f20713fd4a6ebeb5274a5104d233476a7fa13fbc", + "blockHash": "0x37b9bdd82d468cf1231dda02cae652de11110fa947ce4d9e13342975a964d8c0", + "blockNumber": "0x3cf", + "blockTimestamp": "0x6a5dfb24", + "transactionHash": "0xbd194a4ba967f640006df08207da75427137d3d69a0f9ebbbb71d82e4c47d453", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x37b9bdd82d468cf1231dda02cae652de11110fa947ce4d9e13342975a964d8c0", + "blockNumber": "0x3cf", + "blockTimestamp": "0x6a5dfb24", + "transactionHash": "0xbd194a4ba967f640006df08207da75427137d3d69a0f9ebbbb71d82e4c47d453", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xd715873f585a8b667f18af78f1978dc94af3e9e2e6d274a81f950b7c1c57e30977443b8c22dc1541dddb3ca653dae81b96353a7766eeb3ba7fca0847396e8222", + "blockHash": "0x2ddfe04791adc2a04dcc9aef0bfa3bbece7866195911ead47ce2ebc383e3b146", + "blockNumber": "0x3d0", + "blockTimestamp": "0x6a5dfb25", + "transactionHash": "0x90a913b03fea8f09b5eafce171d134fbacc776932441fc5de40b64ce6d5602c7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2ddfe04791adc2a04dcc9aef0bfa3bbece7866195911ead47ce2ebc383e3b146", + "blockNumber": "0x3d0", + "blockTimestamp": "0x6a5dfb25", + "transactionHash": "0x90a913b03fea8f09b5eafce171d134fbacc776932441fc5de40b64ce6d5602c7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x02225315f4461abf084ff95dc45d01a883dc5d5c4a969807b6f1be6a585bd918b005f2afb0adadc1d620d90feae96347d6a996455945eab543286c7eab6dbd9f", + "blockHash": "0x20ebd64f6ab9df3d7f9af5dd1419c3d6e08cd084a02875e64af605e95c9df786", + "blockNumber": "0x3d2", + "blockTimestamp": "0x6a5dfb26", + "transactionHash": "0xb8239187d0e1308679797dcfdd407d952afe685f36770ca1d91fdd7f88a39e14", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x20ebd64f6ab9df3d7f9af5dd1419c3d6e08cd084a02875e64af605e95c9df786", + "blockNumber": "0x3d2", + "blockTimestamp": "0x6a5dfb26", + "transactionHash": "0xb8239187d0e1308679797dcfdd407d952afe685f36770ca1d91fdd7f88a39e14", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x7e466c917a2da84a6a4e8585e95eeaa33bcb66d1e5bda73d3aee64222cf1ece005f01b9e38f0d0ef6182655fd8213b96320a5e3b0739e312ce271cbbf741fafc", + "blockHash": "0x251e70fb074e0dd3d2d95dc0a1b7dd6a770967ebdda353ac5375bddae692a6ab", + "blockNumber": "0x3d3", + "blockTimestamp": "0x6a5dfb27", + "transactionHash": "0xddb7a7e41712281f1172ff5362e02ae2c0ebdfaff1eb3fc738366ed5292b3d3a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x251e70fb074e0dd3d2d95dc0a1b7dd6a770967ebdda353ac5375bddae692a6ab", + "blockNumber": "0x3d3", + "blockTimestamp": "0x6a5dfb27", + "transactionHash": "0xddb7a7e41712281f1172ff5362e02ae2c0ebdfaff1eb3fc738366ed5292b3d3a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xd4b3de072712d535cef4ea2419826ae1295756ee1adefb3e2cb1ad62c0251f70e9e82d80b912abcf97b1a3966ee8231232f60418982dc251bcd4aa1ddce1443b", + "blockHash": "0x43f3a225430f665f34d678f31948c9fa0fb9fefe07fdc9c78d9d1c97f4b91250", + "blockNumber": "0x3d5", + "blockTimestamp": "0x6a5dfb27", + "transactionHash": "0xaf336168c8a2180d5184b16cb21862314cefd5555fc9ab0820b5a6690440d3a9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x43f3a225430f665f34d678f31948c9fa0fb9fefe07fdc9c78d9d1c97f4b91250", + "blockNumber": "0x3d5", + "blockTimestamp": "0x6a5dfb27", + "transactionHash": "0xaf336168c8a2180d5184b16cb21862314cefd5555fc9ab0820b5a6690440d3a9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xfb3866a8f7e3b0ab72517999c0e3ae11ee1697ee538ddb04e2579561d1c7f1509596943413703020049574466cef30eb90a5030a7204ec4e489b7e878e907b25", + "blockHash": "0x1f93762c9dc6795775cfc12083997ad524bd1ff4d1972049c0c411ad911f29c4", + "blockNumber": "0x3d6", + "blockTimestamp": "0x6a5dfb29", + "transactionHash": "0x706660a24192d9bf35d28191cd6fea99619894fc6009f8cc7b766c4912c11e78", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1f93762c9dc6795775cfc12083997ad524bd1ff4d1972049c0c411ad911f29c4", + "blockNumber": "0x3d6", + "blockTimestamp": "0x6a5dfb29", + "transactionHash": "0x706660a24192d9bf35d28191cd6fea99619894fc6009f8cc7b766c4912c11e78", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0x63224ccfb67c06f31928d93e70f2787cb1f8adfd193fdfbc66789233cbcc3244528b05ac4fcc21192a9788f03ede49186a29926062695b0ba7c5c44e423933fe", + "blockHash": "0xdbee567666799535ab2d552a1abe29b549c7861c430d1446efb8769f631eee2d", + "blockNumber": "0x3d8", + "blockTimestamp": "0x6a5dfb29", + "transactionHash": "0x3faccef0fbb1090b337e7e20c1a42b60218d708ef6a3faf7f7869b013a613f8b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdbee567666799535ab2d552a1abe29b549c7861c430d1446efb8769f631eee2d", + "blockNumber": "0x3d8", + "blockTimestamp": "0x6a5dfb29", + "transactionHash": "0x3faccef0fbb1090b337e7e20c1a42b60218d708ef6a3faf7f7869b013a613f8b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xe28f00f31d0eff52d8cb85cf9af1b578592e973681eaa3631bb8085814807865a53c25f80c68ae1386564dc3073f75197a74773e80e79a255d7ea1333cc804d3", + "blockHash": "0xa4907ce8d06e9ac90f79bfae0f115c831cc5d03971f9984d195ffe52f55c6900", + "blockNumber": "0x3d9", + "blockTimestamp": "0x6a5dfb2a", + "transactionHash": "0xbe29799a5ac4f05fe0cff8f703504220a23df27bdb0f6b7e4550d22c1be9820d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa4907ce8d06e9ac90f79bfae0f115c831cc5d03971f9984d195ffe52f55c6900", + "blockNumber": "0x3d9", + "blockTimestamp": "0x6a5dfb2a", + "transactionHash": "0xbe29799a5ac4f05fe0cff8f703504220a23df27bdb0f6b7e4550d22c1be9820d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xd88816c3bae8670c40b53a9cc08d013c2cef96cd1ee710ba4d40677de0d380a4dc24baf80049a9a7a5c5e0bb365673d62b722108ddaf078128fe37be9296f56d", + "blockHash": "0x7b94edac889b4a497838fcb9b45da13ba2cdf942a697578b547271786c48e7c2", + "blockNumber": "0x3db", + "blockTimestamp": "0x6a5dfb2b", + "transactionHash": "0x2499ad68f046bfee32a5dbd2a2c3ea6af611eafb402934384e355ded156b7844", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7b94edac889b4a497838fcb9b45da13ba2cdf942a697578b547271786c48e7c2", + "blockNumber": "0x3db", + "blockTimestamp": "0x6a5dfb2b", + "transactionHash": "0x2499ad68f046bfee32a5dbd2a2c3ea6af611eafb402934384e355ded156b7844", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xfcfb982214e9ca051885234fae2d1394d47ccfab3840449a32d61508bc5887c56f3957a8f92d320b77aaf825540fce760ad8f850cfb7ca879df7661635406dfd", + "blockHash": "0xce7f00f13fc1465c05837659e3634347a5f545293fdc69197a2f8c7126457a5d", + "blockNumber": "0x3dc", + "blockTimestamp": "0x6a5dfb2c", + "transactionHash": "0x3c1d22e98cfb96a33ea7520153c4bf5003491fd037caa20d47d369f324c8cbce", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xce7f00f13fc1465c05837659e3634347a5f545293fdc69197a2f8c7126457a5d", + "blockNumber": "0x3dc", + "blockTimestamp": "0x6a5dfb2c", + "transactionHash": "0x3c1d22e98cfb96a33ea7520153c4bf5003491fd037caa20d47d369f324c8cbce", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9" + ], + "data": "0xcb0fd195c65a1e0c5976a8f6b7f64de4148d20148b90de7247a217947a4f01a1599d475833e21ad417cf2a5e6bab3140ad1d1341f9cdc7874ad1729874bfb675", + "blockHash": "0xd24b0437bd758116b4afd935b84bfc877823e8d8d877cb15dc6990f88a384047", + "blockNumber": "0x3de", + "blockTimestamp": "0x6a5dfb2c", + "transactionHash": "0xae4d52e857143be85e2327bdcc6d69831da9bfcf885b6ab6019def7d5d4cb008", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd24b0437bd758116b4afd935b84bfc877823e8d8d877cb15dc6990f88a384047", + "blockNumber": "0x3de", + "blockTimestamp": "0x6a5dfb2c", + "transactionHash": "0xae4d52e857143be85e2327bdcc6d69831da9bfcf885b6ab6019def7d5d4cb008", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb764dadead6d040e4aa840bbd07f9c938cf4fe7b5158e56b900a68709cbbeb61", + "blockNumber": "0x3df", + "blockTimestamp": "0x6a5dfb2d", + "transactionHash": "0xd7b94b4646b88103ba5ed69e7a92ceb374f80beb2b2226038cf45506602782ad", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xe232ed9e61037dae99ec3c8a9a56f5f82cdbd31499126fab04592273d36398c9", + "0x249aa34bdcc24fceda18de0c67c9994f5297415a113f39df42349ce4d473b4f6", + "0x7ed6c4201672c24a03dbdb6c867b1922815705bd09039712cc564f19b48183ea" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0x5a2ea0d5122940f48a7029a0bef90deb5d5334560add064ba5265b889a4f7aa7", + "blockNumber": "0x3e1", + "blockTimestamp": "0x6a5dfb2e", + "transactionHash": "0x0586c8a078e87fb96d4ada2843f1279e793a8e4772c3f7260c92ea9792a4e314", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x6ec4c9d2fd20ce509f40737561156c1091834121", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003147e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5a2ea0d5122940f48a7029a0bef90deb5d5334560add064ba5265b889a4f7aa7", + "blockNumber": "0x3e1", + "blockTimestamp": "0x6a5dfb2e", + "transactionHash": "0x0586c8a078e87fb96d4ada2843f1279e793a8e4772c3f7260c92ea9792a4e314", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e2000000000000000000000000000000000000000000000000000000006a5dfb2f8f3b756a8e1a37e91a613764a74737a8a8080a97a7267f2195e708aa050b468f00000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xef3f3811009f9b17481a8883d3bc2d586c956be028f86e5e967239715eed3c75", + "blockNumber": "0x3e2", + "blockTimestamp": "0x6a5dfb2f", + "transactionHash": "0x24019d3d80804dcba1802cb2d6cd01019525278fb89c132485c7358c2dfce74c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000008" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e3000000000000000000000000000000000000000000000000000000006a5dfb2fbdd15acd3d52f08ec981bfddee291f24d77be6941e7f6f3c7cc5d51c017de67a00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3a877167dd3a1d1be6cdcec121d875ee94705cc56ae9124c3dc303ac244f9818", + "blockNumber": "0x3e3", + "blockTimestamp": "0x6a5dfb2f", + "transactionHash": "0x473ea60286cd932b4f9458da75a5ced78935b4b1eda5f78bd7489d496cb97d9f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x0000000000000000000000000000000000000000000000000000000000000009" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e4000000000000000000000000000000000000000000000000000000006a5dfb2f4f510cf60f2dcd17e2089b979614de5a862018c3f3e73e8a380629531e8afd0200000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf0981ad66c71477ba8db1a30b8e92b98f394bae937cf76a697439c130623d883", + "blockNumber": "0x3e4", + "blockTimestamp": "0x6a5dfb2f", + "transactionHash": "0x967cc93daa93f00da68e9f4689c764b7a6ddeb8e78031b149260a109a361c8e0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xb09e24bcd7587187513e16028ba7af281ad084ad3548db4b59e3aead6ba36b21", + "0x6dd387a67968e6991d193ed3c065d438be85e7bd21e5113cbd12b094b4265dc0", + "0x64402689273954a4c2477ffa8737d08da78862e1313e2d7ee24550ec62cf0264" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x023272fc8b0fd1bc0cced7ad6c9fcbb9e6ed031dcdce20dbd6bc7c1969929eda", + "blockNumber": "0x4e5", + "blockTimestamp": "0x6a5dfb34", + "transactionHash": "0xc1476d3c9ceae414191c2cd43da77e0ec0056d60cd51b755066bbf61fcd499ed", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xd89b879abf354b3912f764fbd597c51d6e33340d", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x023272fc8b0fd1bc0cced7ad6c9fcbb9e6ed031dcdce20dbd6bc7c1969929eda", + "blockNumber": "0x4e5", + "blockTimestamp": "0x6a5dfb34", + "transactionHash": "0xc1476d3c9ceae414191c2cd43da77e0ec0056d60cd51b755066bbf61fcd499ed", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x1f5c6d6ef1452777638d0e3d7c8d0b5afa67e3e8a9dde6554ea0b88a68b0528c", + "blockNumber": "0x4e6", + "blockTimestamp": "0x6a5dfb35", + "transactionHash": "0x7ae9dc02cc87b576f7640f6d029e7569fa6dbb9c50da82a8d6a3a4ae2b51508e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x98bcd7e74c1ddd4a3eb1b5afa0c077d7554d1865", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001ba9c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1f5c6d6ef1452777638d0e3d7c8d0b5afa67e3e8a9dde6554ea0b88a68b0528c", + "blockNumber": "0x4e6", + "blockTimestamp": "0x6a5dfb35", + "transactionHash": "0x7ae9dc02cc87b576f7640f6d029e7569fa6dbb9c50da82a8d6a3a4ae2b51508e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0x10ee65b35092cb4b63c1f86b25c171c3f2ea17b638bf1c2fbd0192017a2f2801", + "blockNumber": "0x4e7", + "blockTimestamp": "0x6a5dfb36", + "transactionHash": "0xca5d9329396879273b83aaa7806343489f4808505fa337dc709180d4bc417164", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x00000000000000000000000006a788f8570f902de1b36b46569999d706bccdd8", + "blockHash": "0xe947c1d89d83ae7d1648c6090e1b228215291f1c14dcb6684a1303acc08aa630", + "blockNumber": "0x4e8", + "blockTimestamp": "0x6a5dfb36", + "transactionHash": "0x535e467cb87a6b7a1a8af36faeff928a8893516beebe1943698888d9234b524d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000a01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c600000000000000000000000006a788f8570f902de1b36b46569999d706bccdd8", + "blockHash": "0xe947c1d89d83ae7d1648c6090e1b228215291f1c14dcb6684a1303acc08aa630", + "blockNumber": "0x4e8", + "blockTimestamp": "0x6a5dfb36", + "transactionHash": "0x535e467cb87a6b7a1a8af36faeff928a8893516beebe1943698888d9234b524d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x46f18ebf3f5b20a8283b7cdccfeeb8c2cdb409c0ee97ee47978cab1612be8bfb", + "blockNumber": "0x4eb", + "blockTimestamp": "0x6a5dfb40", + "transactionHash": "0x04cc3d78ce8fbd331ebc1565fc5df2121b11f68efc0397e9f552b2c6b58848ca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x850fd05cc9ab8dd10b0bdf7a32ee3a94340ad83396f4b40f9317cac98dad4db4", + "blockNumber": "0x4ec", + "blockTimestamp": "0x6a5dfb40", + "transactionHash": "0x6ac77eeb7f7179420e2fbc9726cef2e734ad2b603afe70cdd5abd8eb28e07c12", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906" + ], + "data": "0x5da3e2f42204fa3d419c647a5c1f7c2a7cc87060d26eac1fc9045d1c946adcd901c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x0ba3a1839d8200aae75ca0c6dc086d83522d78af065316d9ca567e01799591d3", + "blockNumber": "0x4f1", + "blockTimestamp": "0x6a5dfb44", + "transactionHash": "0x92c264d28223e945a13802ea6e08281c5ccdd0b97323ef661a9bc4c203bc10ab", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x5da3e2f42204fa3d419c647a5c1f7c2a7cc87060d26eac1fc9045d1c946adcd9" + ], + "data": "0x844164bf5e511057955e24862bc29f88f1c83af9bc93fc71c9c28e8515cb3f07", + "blockHash": "0x0ba3a1839d8200aae75ca0c6dc086d83522d78af065316d9ca567e01799591d3", + "blockNumber": "0x4f1", + "blockTimestamp": "0x6a5dfb44", + "transactionHash": "0x92c264d28223e945a13802ea6e08281c5ccdd0b97323ef661a9bc4c203bc10ab", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x844164bf5e511057955e24862bc29f88f1c83af9bc93fc71c9c28e8515cb3f07fae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442", + "blockHash": "0x4d221d67a505eb15cfafe7622f8eece28f11621b09efea77782ba8e7005f7089", + "blockNumber": "0x4f3", + "blockTimestamp": "0x6a5dfb44", + "transactionHash": "0xc0b0fd5ada8e5dd475d5c9b16862c5593d2ca8d5cde4901bdf8961fa870fd9d2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4d221d67a505eb15cfafe7622f8eece28f11621b09efea77782ba8e7005f7089", + "blockNumber": "0x4f3", + "blockTimestamp": "0x6a5dfb44", + "transactionHash": "0xc0b0fd5ada8e5dd475d5c9b16862c5593d2ca8d5cde4901bdf8961fa870fd9d2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c444423629603849bbe9eb51d192a803deecfb4e9a5df9d8b0a671d092adfc3a86d186", + "blockHash": "0x15f176380e91ecdcc0173bbe819ee3013d4c64ff314d0358420a42f7d1d9db23", + "blockNumber": "0x4f4", + "blockTimestamp": "0x6a5dfb45", + "transactionHash": "0x3973dab8463d1723c1e820a91bbbafb47c5b018a24d8c30f7c6facdc2ad8ed1c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x15f176380e91ecdcc0173bbe819ee3013d4c64ff314d0358420a42f7d1d9db23", + "blockNumber": "0x4f4", + "blockTimestamp": "0x6a5dfb45", + "transactionHash": "0x3973dab8463d1723c1e820a91bbbafb47c5b018a24d8c30f7c6facdc2ad8ed1c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x3629603849bbe9eb51d192a803deecfb4e9a5df9d8b0a671d092adfc3a86d1866c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd602", + "blockHash": "0x9fe01d236d51b53b2a14fa4b7296fd478790d967f9492175c6f23c3d7710c138", + "blockNumber": "0x4f6", + "blockTimestamp": "0x6a5dfb45", + "transactionHash": "0xa7c9ec64d71d05e3627cd0ea66b917c1a9000a898617fce585464b9645be4494", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9fe01d236d51b53b2a14fa4b7296fd478790d967f9492175c6f23c3d7710c138", + "blockNumber": "0x4f6", + "blockTimestamp": "0x6a5dfb45", + "transactionHash": "0xa7c9ec64d71d05e3627cd0ea66b917c1a9000a898617fce585464b9645be4494", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x6c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd6028d4f1179835db87c0e46ede92cffc3a6341409cacebb04700d8db1cd02a6fd5d", + "blockHash": "0xcfeed78a8ebc92c7dfaaa6acc527aaaa6e6fcbda4e8f7106d09c2f89fe5e448f", + "blockNumber": "0x4f7", + "blockTimestamp": "0x6a5dfb46", + "transactionHash": "0xfa05b922f16d8711b51f193b2b227ed30a25856720a3352689440dce4955ba9d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcfeed78a8ebc92c7dfaaa6acc527aaaa6e6fcbda4e8f7106d09c2f89fe5e448f", + "blockNumber": "0x4f7", + "blockTimestamp": "0x6a5dfb46", + "transactionHash": "0xfa05b922f16d8711b51f193b2b227ed30a25856720a3352689440dce4955ba9d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x8d4f1179835db87c0e46ede92cffc3a6341409cacebb04700d8db1cd02a6fd5d7df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0ca", + "blockHash": "0x8ab8d72947a1f03b88a8d63c270988a4c50e6f5b54dc374beba4c5cdd8d0c9a2", + "blockNumber": "0x4f9", + "blockTimestamp": "0x6a5dfb46", + "transactionHash": "0xdc7a95d12caaa929acd0eec639178466df485219fbea845f07b235eda65bda51", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8ab8d72947a1f03b88a8d63c270988a4c50e6f5b54dc374beba4c5cdd8d0c9a2", + "blockNumber": "0x4f9", + "blockTimestamp": "0x6a5dfb46", + "transactionHash": "0xdc7a95d12caaa929acd0eec639178466df485219fbea845f07b235eda65bda51", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x7df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0ca97948dea3a6f399a77abde8ef996377b9593674d403d7661e565b0c77b123bdf", + "blockHash": "0x48d126419eb0455867a93819aa00221dba9e96a6071776d04d87ce41627bcb16", + "blockNumber": "0x4fa", + "blockTimestamp": "0x6a5dfb47", + "transactionHash": "0x2b001f92bbf7c816f1d89075ba8c240d26e3804a54ae6a9634fc6ba636e4208b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x48d126419eb0455867a93819aa00221dba9e96a6071776d04d87ce41627bcb16", + "blockNumber": "0x4fa", + "blockTimestamp": "0x6a5dfb47", + "transactionHash": "0x2b001f92bbf7c816f1d89075ba8c240d26e3804a54ae6a9634fc6ba636e4208b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x97948dea3a6f399a77abde8ef996377b9593674d403d7661e565b0c77b123bdf10f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583", + "blockHash": "0xf9158b02b70a1a26a02313894e47177f5a302548f0a11343aca07e165734988e", + "blockNumber": "0x4fc", + "blockTimestamp": "0x6a5dfb47", + "transactionHash": "0x44b566c53d2eece70c1d410b37a326be6a2e9edbb24a690d49afa39499b655eb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf9158b02b70a1a26a02313894e47177f5a302548f0a11343aca07e165734988e", + "blockNumber": "0x4fc", + "blockTimestamp": "0x6a5dfb47", + "transactionHash": "0x44b566c53d2eece70c1d410b37a326be6a2e9edbb24a690d49afa39499b655eb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x10f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c2358321e8a2549ecb093ba2266065d9aa080fdd9c6035bb97f27e938ef9743ea204cb", + "blockHash": "0x03083b3ab492b58eb2227e4b1d48a448b1d92190e3a2474a6254efa11d3837d9", + "blockNumber": "0x4fd", + "blockTimestamp": "0x6a5dfb48", + "transactionHash": "0xc796e665cd506694ac1f79e625405eca2a7bf4344f59115eb52148161307be8b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x03083b3ab492b58eb2227e4b1d48a448b1d92190e3a2474a6254efa11d3837d9", + "blockNumber": "0x4fd", + "blockTimestamp": "0x6a5dfb48", + "transactionHash": "0xc796e665cd506694ac1f79e625405eca2a7bf4344f59115eb52148161307be8b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x21e8a2549ecb093ba2266065d9aa080fdd9c6035bb97f27e938ef9743ea204cb9a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6", + "blockHash": "0x2ee414c31cee3aa2461b29c95124b16880ddec7b62389a9af453c7c3621d4445", + "blockNumber": "0x4ff", + "blockTimestamp": "0x6a5dfb49", + "transactionHash": "0x3aa2905dd39f559f163d71d0d4ecdd8923dc60ebc86c0f3d4f4df43b92097371", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2ee414c31cee3aa2461b29c95124b16880ddec7b62389a9af453c7c3621d4445", + "blockNumber": "0x4ff", + "blockTimestamp": "0x6a5dfb49", + "transactionHash": "0x3aa2905dd39f559f163d71d0d4ecdd8923dc60ebc86c0f3d4f4df43b92097371", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x9a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b62d85f01d89f77968228abae44ed3bbe5d6c4d509af9b8d3dd59c04d289c10665", + "blockHash": "0x0a39800e04affcf3594dedcc33580e268cc4cea8bb25f595e0f6214f74d59040", + "blockNumber": "0x500", + "blockTimestamp": "0x6a5dfb4a", + "transactionHash": "0xf03be52f6d338a13e78e5f754b735ea1a9d85c5324e6400bff8ebe0f86cf7d65", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0a39800e04affcf3594dedcc33580e268cc4cea8bb25f595e0f6214f74d59040", + "blockNumber": "0x500", + "blockTimestamp": "0x6a5dfb4a", + "transactionHash": "0xf03be52f6d338a13e78e5f754b735ea1a9d85c5324e6400bff8ebe0f86cf7d65", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x2d85f01d89f77968228abae44ed3bbe5d6c4d509af9b8d3dd59c04d289c10665120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1dea", + "blockHash": "0x2e061d73ed5c34db6854eba15818e0fd64ef731b7b825792d3a9dbbef26ea6ce", + "blockNumber": "0x502", + "blockTimestamp": "0x6a5dfb4a", + "transactionHash": "0x7d50040e932e87c9a626dd58023d77c2c2a11058f3532f5a2c3a89548e68179d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2e061d73ed5c34db6854eba15818e0fd64ef731b7b825792d3a9dbbef26ea6ce", + "blockNumber": "0x502", + "blockTimestamp": "0x6a5dfb4a", + "transactionHash": "0x7d50040e932e87c9a626dd58023d77c2c2a11058f3532f5a2c3a89548e68179d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1deac883838e63092ddc39ccf9653f06a905bc3b3567bb6e63f4a23b6551863ba96f", + "blockHash": "0x05f4ef3c8adb297a74283bab180ef2a8534d4dda4feee4183df4e489aa7634ab", + "blockNumber": "0x503", + "blockTimestamp": "0x6a5dfb4b", + "transactionHash": "0x5033fe86d0dec20aa8262c08e3375d93a175f7d61b39f0605277724580aa4e56", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x05f4ef3c8adb297a74283bab180ef2a8534d4dda4feee4183df4e489aa7634ab", + "blockNumber": "0x503", + "blockTimestamp": "0x6a5dfb4b", + "transactionHash": "0x5033fe86d0dec20aa8262c08e3375d93a175f7d61b39f0605277724580aa4e56", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xc883838e63092ddc39ccf9653f06a905bc3b3567bb6e63f4a23b6551863ba96f4397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102", + "blockHash": "0x13b464108c8b30caa2a73de152c1d1b11c65943a543e5d439b47c17d6d030b25", + "blockNumber": "0x505", + "blockTimestamp": "0x6a5dfb4b", + "transactionHash": "0x8a46f20782f04417e19dd98c270954d8d64cc893e652091995f50ce0d973337d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x13b464108c8b30caa2a73de152c1d1b11c65943a543e5d439b47c17d6d030b25", + "blockNumber": "0x505", + "blockTimestamp": "0x6a5dfb4b", + "transactionHash": "0x8a46f20782f04417e19dd98c270954d8d64cc893e652091995f50ce0d973337d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x4397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d10269e56d56768e53c153951ea48f8b34b715a0fdcbe238d97487fbd10869bc393e", + "blockHash": "0x1a4c0fe029a87e40adbb83baa4186a14c272f4fa99397a7284b34e971fef7a55", + "blockNumber": "0x506", + "blockTimestamp": "0x6a5dfb4c", + "transactionHash": "0xfede04fe61c16839d46bb16c479e8d989bf54035b2355b2348250bc6ab6be46e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1a4c0fe029a87e40adbb83baa4186a14c272f4fa99397a7284b34e971fef7a55", + "blockNumber": "0x506", + "blockTimestamp": "0x6a5dfb4c", + "transactionHash": "0xfede04fe61c16839d46bb16c479e8d989bf54035b2355b2348250bc6ab6be46e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x69e56d56768e53c153951ea48f8b34b715a0fdcbe238d97487fbd10869bc393e15797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d84", + "blockHash": "0xae39f0685826c0983b448c26b5468bbe91d336cb6a92b1dd2079d5f0d0782aa9", + "blockNumber": "0x508", + "blockTimestamp": "0x6a5dfb4c", + "transactionHash": "0xb37faf7b2dc6e19df0a47a7fdbdf3e1ac7970119aebf048220161873b826a320", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xae39f0685826c0983b448c26b5468bbe91d336cb6a92b1dd2079d5f0d0782aa9", + "blockNumber": "0x508", + "blockTimestamp": "0x6a5dfb4c", + "transactionHash": "0xb37faf7b2dc6e19df0a47a7fdbdf3e1ac7970119aebf048220161873b826a320", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x15797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d84d04b4fee374cdf55982bcc5e535139b641c8fe6805e0cb4ff2570cfa76a7c406", + "blockHash": "0x1cfd84d4b5a3c426fd2f8e94f09b1ab0d1ec34fe0592095c8e8156b1868bd87a", + "blockNumber": "0x509", + "blockTimestamp": "0x6a5dfb4d", + "transactionHash": "0xa3fa4859957337a7b81c78ab1aeb318ed1bc397828f859d381715cf724a9dac2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1cfd84d4b5a3c426fd2f8e94f09b1ab0d1ec34fe0592095c8e8156b1868bd87a", + "blockNumber": "0x509", + "blockTimestamp": "0x6a5dfb4d", + "transactionHash": "0xa3fa4859957337a7b81c78ab1aeb318ed1bc397828f859d381715cf724a9dac2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xd04b4fee374cdf55982bcc5e535139b641c8fe6805e0cb4ff2570cfa76a7c406779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5a", + "blockHash": "0x192bbaa8ad04f917f663f46adba854b3f51c7b7ea75bb65714e99cd73200bd9b", + "blockNumber": "0x50b", + "blockTimestamp": "0x6a5dfb4d", + "transactionHash": "0xb76b0d4128ddc3b29dc1523fb852f786e9e2ed6e7d45e12d403992b0016b4fb3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x192bbaa8ad04f917f663f46adba854b3f51c7b7ea75bb65714e99cd73200bd9b", + "blockNumber": "0x50b", + "blockTimestamp": "0x6a5dfb4d", + "transactionHash": "0xb76b0d4128ddc3b29dc1523fb852f786e9e2ed6e7d45e12d403992b0016b4fb3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5a33133bf19dec0108c24e625fbb810b7c1a791a88a68e368bf0c55aab3fbcddec", + "blockHash": "0x9f35aca0ff04e42419dc6cf2ed0c333785badf55a34f201e9b6d133a3e6beb75", + "blockNumber": "0x50c", + "blockTimestamp": "0x6a5dfb4f", + "transactionHash": "0xfa5f78deaf9f98495d60c03f369b30fdf231c9fe8babf09ac69e58168315adf7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9f35aca0ff04e42419dc6cf2ed0c333785badf55a34f201e9b6d133a3e6beb75", + "blockNumber": "0x50c", + "blockTimestamp": "0x6a5dfb4f", + "transactionHash": "0xfa5f78deaf9f98495d60c03f369b30fdf231c9fe8babf09ac69e58168315adf7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x33133bf19dec0108c24e625fbb810b7c1a791a88a68e368bf0c55aab3fbcddecb262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203", + "blockHash": "0x5d22f5106fa80b9574da54c64f4c1908b669f6c14c258497e26b7378b2ba9e51", + "blockNumber": "0x50e", + "blockTimestamp": "0x6a5dfb4f", + "transactionHash": "0x1c57a21dff08cc52d828c7511fadc610f5f552daf3979a7401dd5d185df6ec44", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5d22f5106fa80b9574da54c64f4c1908b669f6c14c258497e26b7378b2ba9e51", + "blockNumber": "0x50e", + "blockTimestamp": "0x6a5dfb4f", + "transactionHash": "0x1c57a21dff08cc52d828c7511fadc610f5f552daf3979a7401dd5d185df6ec44", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xb262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203a28f62a1d763f3b834a0831cd9c444b67ce937d7e2a307ee5a039bca26c88df7", + "blockHash": "0xe9e463ac2b4a4ed51c3c404dcbcae6c1c3dd7f0ef99f2ec07b07cf9242b3e3e2", + "blockNumber": "0x50f", + "blockTimestamp": "0x6a5dfb50", + "transactionHash": "0xdf1ecdb09d90a7bb7075ccb4a0abd0bf0a2999ad69ae168e5b0dc28d7c737222", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe9e463ac2b4a4ed51c3c404dcbcae6c1c3dd7f0ef99f2ec07b07cf9242b3e3e2", + "blockNumber": "0x50f", + "blockTimestamp": "0x6a5dfb50", + "transactionHash": "0xdf1ecdb09d90a7bb7075ccb4a0abd0bf0a2999ad69ae168e5b0dc28d7c737222", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xa28f62a1d763f3b834a0831cd9c444b67ce937d7e2a307ee5a039bca26c88df76564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a", + "blockHash": "0xf5e34241c511719caf23591d9bc615faf14dd8d2e9be0b976d86565e4333c2f0", + "blockNumber": "0x511", + "blockTimestamp": "0x6a5dfb50", + "transactionHash": "0xb864d84d86b4ce09d2fc068cc4353654013faae3635f0522d00054b3cbf11eff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf5e34241c511719caf23591d9bc615faf14dd8d2e9be0b976d86565e4333c2f0", + "blockNumber": "0x511", + "blockTimestamp": "0x6a5dfb50", + "transactionHash": "0xb864d84d86b4ce09d2fc068cc4353654013faae3635f0522d00054b3cbf11eff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x6564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2ac07dbb2f2112a87b1c2ea264f12c3ae77001e336f88fbda6138f57bc829c6b71", + "blockHash": "0x0ce4fc1a82aa7481859fe4e0534b90b21b5c0978866548789216f34f2ee86294", + "blockNumber": "0x512", + "blockTimestamp": "0x6a5dfb51", + "transactionHash": "0x49d6d5b5b78730758581fdcd4698604ed117d2654d13ac21ed163bb70c55111b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0ce4fc1a82aa7481859fe4e0534b90b21b5c0978866548789216f34f2ee86294", + "blockNumber": "0x512", + "blockTimestamp": "0x6a5dfb51", + "transactionHash": "0x49d6d5b5b78730758581fdcd4698604ed117d2654d13ac21ed163bb70c55111b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xc07dbb2f2112a87b1c2ea264f12c3ae77001e336f88fbda6138f57bc829c6b71e84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730", + "blockHash": "0xa947536e4b0cf8a48b1b8179f5714e903682de895847d272d460811d94970b12", + "blockNumber": "0x514", + "blockTimestamp": "0x6a5dfb51", + "transactionHash": "0x4fd58872d8942700c38995c251d9322fa1f3eff71aff7a2d528d40e532c4e393", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa947536e4b0cf8a48b1b8179f5714e903682de895847d272d460811d94970b12", + "blockNumber": "0x514", + "blockTimestamp": "0x6a5dfb51", + "transactionHash": "0x4fd58872d8942700c38995c251d9322fa1f3eff71aff7a2d528d40e532c4e393", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed7305a96fc605f861faa5eb9f0c72d1eb36fef946876044ab8aa9546a8d645b7f39c", + "blockHash": "0x99bae402d08549c4ec670cb51723bc5e34c53600e4498a20372b5c3836d3b097", + "blockNumber": "0x515", + "blockTimestamp": "0x6a5dfb52", + "transactionHash": "0x5fd405e45ccf278269c45f4c5ba31a5654bb160e3641f3dd4504ed6333ea3b7c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x99bae402d08549c4ec670cb51723bc5e34c53600e4498a20372b5c3836d3b097", + "blockNumber": "0x515", + "blockTimestamp": "0x6a5dfb52", + "transactionHash": "0x5fd405e45ccf278269c45f4c5ba31a5654bb160e3641f3dd4504ed6333ea3b7c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x5a96fc605f861faa5eb9f0c72d1eb36fef946876044ab8aa9546a8d645b7f39ccb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882", + "blockHash": "0xcb4dd33c93e5e064c47636198453ee5be9887504f9917e1e65078843f93d2cb0", + "blockNumber": "0x517", + "blockTimestamp": "0x6a5dfb52", + "transactionHash": "0x43dabbd7cc8b03efa1b379309f675bef477dd36f357f47cdf9541f65f787b546", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcb4dd33c93e5e064c47636198453ee5be9887504f9917e1e65078843f93d2cb0", + "blockNumber": "0x517", + "blockTimestamp": "0x6a5dfb52", + "transactionHash": "0x43dabbd7cc8b03efa1b379309f675bef477dd36f357f47cdf9541f65f787b546", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xcb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882c0e34ac6f77db554a43663765557a98a34e2246b668722e6adee13ffaad1d955", + "blockHash": "0x5f96978ee0a421123e193fc7d9d06f1d0dae9d18ae92c13a55567830e82e0a07", + "blockNumber": "0x518", + "blockTimestamp": "0x6a5dfb53", + "transactionHash": "0x02bca4500080996790827c02b7e4f6c83f4f8eeecf22d0dd2b0a5ac3645558d0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5f96978ee0a421123e193fc7d9d06f1d0dae9d18ae92c13a55567830e82e0a07", + "blockNumber": "0x518", + "blockTimestamp": "0x6a5dfb53", + "transactionHash": "0x02bca4500080996790827c02b7e4f6c83f4f8eeecf22d0dd2b0a5ac3645558d0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xc0e34ac6f77db554a43663765557a98a34e2246b668722e6adee13ffaad1d95569a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693", + "blockHash": "0x4c41394e21cadbce402d430106b8f244b75cfc744b8a8f0d58f5b43e179f6b66", + "blockNumber": "0x51a", + "blockTimestamp": "0x6a5dfb54", + "transactionHash": "0xdb57ca0bf99cd37b2d93253441763a4c5812fd60215d61ce835a01de2295e416", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4c41394e21cadbce402d430106b8f244b75cfc744b8a8f0d58f5b43e179f6b66", + "blockNumber": "0x51a", + "blockTimestamp": "0x6a5dfb54", + "transactionHash": "0xdb57ca0bf99cd37b2d93253441763a4c5812fd60215d61ce835a01de2295e416", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693a567c90f3d9add24c781f91382042095c9ecc20c7f39499b831d93f2ac935700", + "blockHash": "0x2c9779ed1b80aea18eec814328a88e7cc49705654c294c6a09ddb56c9a3ad94a", + "blockNumber": "0x51b", + "blockTimestamp": "0x6a5dfb55", + "transactionHash": "0x8672d9def2e2e521e9217ebd6a857adad3b85f6ff2968e828ac6986cd70228e4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2c9779ed1b80aea18eec814328a88e7cc49705654c294c6a09ddb56c9a3ad94a", + "blockNumber": "0x51b", + "blockTimestamp": "0x6a5dfb55", + "transactionHash": "0x8672d9def2e2e521e9217ebd6a857adad3b85f6ff2968e828ac6986cd70228e4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xa567c90f3d9add24c781f91382042095c9ecc20c7f39499b831d93f2ac9357001138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37f", + "blockHash": "0x43cceada5b2423c6b598ff5977efa48dcd391e78aea7cd85997dcff7d844f25b", + "blockNumber": "0x51d", + "blockTimestamp": "0x6a5dfb55", + "transactionHash": "0xa949b73a2cb1def86c83c873035dd6cc59b5c3cae7018ff41edcab6422fa831a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x43cceada5b2423c6b598ff5977efa48dcd391e78aea7cd85997dcff7d844f25b", + "blockNumber": "0x51d", + "blockTimestamp": "0x6a5dfb55", + "transactionHash": "0xa949b73a2cb1def86c83c873035dd6cc59b5c3cae7018ff41edcab6422fa831a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x1138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37f3432785f1c3637cbe08a384a10694f232d8f9e43f66b2d9323da1e137376b1de", + "blockHash": "0x80b4205125e218f9dca1a5c9bfcb38451a466e5297e1c38673b326b23674e2fd", + "blockNumber": "0x51e", + "blockTimestamp": "0x6a5dfb56", + "transactionHash": "0x59b2df083cba67c4ffec445a98045c9407dcead7b66091dc1b10d090bb899951", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x80b4205125e218f9dca1a5c9bfcb38451a466e5297e1c38673b326b23674e2fd", + "blockNumber": "0x51e", + "blockTimestamp": "0x6a5dfb56", + "transactionHash": "0x59b2df083cba67c4ffec445a98045c9407dcead7b66091dc1b10d090bb899951", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x3432785f1c3637cbe08a384a10694f232d8f9e43f66b2d9323da1e137376b1dec1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba633", + "blockHash": "0x323de1b75be4fe64d55e5f436b177c97a42e6ea32ccf016b28352f063b6f49e7", + "blockNumber": "0x520", + "blockTimestamp": "0x6a5dfb56", + "transactionHash": "0x3e9a43cca6ad443a7f05b07afc30e3c129ba50e8774949bcc0a7d4d5f165d5ce", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x323de1b75be4fe64d55e5f436b177c97a42e6ea32ccf016b28352f063b6f49e7", + "blockNumber": "0x520", + "blockTimestamp": "0x6a5dfb56", + "transactionHash": "0x3e9a43cca6ad443a7f05b07afc30e3c129ba50e8774949bcc0a7d4d5f165d5ce", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba633e3ce2c91268f452e4dbb5d07dcfd7aa7f660d631e247e691dc889938d072b11c", + "blockHash": "0x79dae09525ac932bbdbbda7824b390a75704869ec0b9fcd4a25cdaac423e86a3", + "blockNumber": "0x521", + "blockTimestamp": "0x6a5dfb57", + "transactionHash": "0xcc6f487e230f4cb89268fa42fa03dc1f84464962857ef1331a1c40775466b9bf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x79dae09525ac932bbdbbda7824b390a75704869ec0b9fcd4a25cdaac423e86a3", + "blockNumber": "0x521", + "blockTimestamp": "0x6a5dfb57", + "transactionHash": "0xcc6f487e230f4cb89268fa42fa03dc1f84464962857ef1331a1c40775466b9bf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xe3ce2c91268f452e4dbb5d07dcfd7aa7f660d631e247e691dc889938d072b11c02686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca", + "blockHash": "0x14eb096f8aed93d2eb5921209128e78589a86cb8c08f041a5056b12421f79277", + "blockNumber": "0x523", + "blockTimestamp": "0x6a5dfb58", + "transactionHash": "0x101f40e065db1738a94c9f4450c5cc49bf166027db0e5064c7ecf2818d01f942", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x14eb096f8aed93d2eb5921209128e78589a86cb8c08f041a5056b12421f79277", + "blockNumber": "0x523", + "blockTimestamp": "0x6a5dfb58", + "transactionHash": "0x101f40e065db1738a94c9f4450c5cc49bf166027db0e5064c7ecf2818d01f942", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x02686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca08bdd614eaba3721a69cbdab4733237c8a4e8f0f75b59c5f93d91a4ed9102d6f", + "blockHash": "0x9cd8998fe0f1c34f45fd2c60160fc63b67e2dfa8e049189220b10edaec2e0e72", + "blockNumber": "0x524", + "blockTimestamp": "0x6a5dfb58", + "transactionHash": "0x823261a27842c11caf07409694e60b2b6bce09be38883d9527ca8ef9b5527687", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9cd8998fe0f1c34f45fd2c60160fc63b67e2dfa8e049189220b10edaec2e0e72", + "blockNumber": "0x524", + "blockTimestamp": "0x6a5dfb58", + "transactionHash": "0x823261a27842c11caf07409694e60b2b6bce09be38883d9527ca8ef9b5527687", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x08bdd614eaba3721a69cbdab4733237c8a4e8f0f75b59c5f93d91a4ed9102d6f37a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f1", + "blockHash": "0x907779f5c34e162506d796d9466ff844625e4afb9e0c0b3bee05b17c0527ab34", + "blockNumber": "0x526", + "blockTimestamp": "0x6a5dfb59", + "transactionHash": "0x8b810ee0a1dabfdb8fcc6f5ef3264179df032be83f1ad916bdb025ee74c03f22", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x907779f5c34e162506d796d9466ff844625e4afb9e0c0b3bee05b17c0527ab34", + "blockNumber": "0x526", + "blockTimestamp": "0x6a5dfb59", + "transactionHash": "0x8b810ee0a1dabfdb8fcc6f5ef3264179df032be83f1ad916bdb025ee74c03f22", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x37a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f18c98e6ff50aa35801b7308d7552072b7062c33886a8658221115d561e6b1b307", + "blockHash": "0x59d01aff25e0e2c41f0a17d2a6a6dd8cffc829342e0c8735ed0194ad90d22d1b", + "blockNumber": "0x527", + "blockTimestamp": "0x6a5dfb5a", + "transactionHash": "0xa7e2812768d9afd8d3fff8e2f6a5edcf4fb31c21c18d7dbfdfc33a54d21b6459", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x59d01aff25e0e2c41f0a17d2a6a6dd8cffc829342e0c8735ed0194ad90d22d1b", + "blockNumber": "0x527", + "blockTimestamp": "0x6a5dfb5a", + "transactionHash": "0xa7e2812768d9afd8d3fff8e2f6a5edcf4fb31c21c18d7dbfdfc33a54d21b6459", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x8c98e6ff50aa35801b7308d7552072b7062c33886a8658221115d561e6b1b307cbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689", + "blockHash": "0xd4a3dc6b8fa91a74ceac41957f3318b8fc52d4ee13da84662f9f3f205774dfa9", + "blockNumber": "0x529", + "blockTimestamp": "0x6a5dfb5b", + "transactionHash": "0x1a9bccc6aaf1dd6ec2f7c299a1c4d6f9f1ca0198dfb08f436d8b0b72e28927a7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd4a3dc6b8fa91a74ceac41957f3318b8fc52d4ee13da84662f9f3f205774dfa9", + "blockNumber": "0x529", + "blockTimestamp": "0x6a5dfb5b", + "transactionHash": "0x1a9bccc6aaf1dd6ec2f7c299a1c4d6f9f1ca0198dfb08f436d8b0b72e28927a7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xcbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689e62ae6f1e14754f3696b61f220be2c83e693e23debfe0582b7e2bbc8a76cd323", + "blockHash": "0x3b7f4067ef83a931c781f9839b4c72454fe21bccae5b0b66a5306ceb4cf222ae", + "blockNumber": "0x52a", + "blockTimestamp": "0x6a5dfb5b", + "transactionHash": "0x354b698629a4694d9c15e0034256e60c45e26c5eb7aa5b9244becd4514999c17", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3b7f4067ef83a931c781f9839b4c72454fe21bccae5b0b66a5306ceb4cf222ae", + "blockNumber": "0x52a", + "blockTimestamp": "0x6a5dfb5b", + "transactionHash": "0x354b698629a4694d9c15e0034256e60c45e26c5eb7aa5b9244becd4514999c17", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xe62ae6f1e14754f3696b61f220be2c83e693e23debfe0582b7e2bbc8a76cd32303e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d", + "blockHash": "0x10c3c873f1e42bb73dc392fde53cfd17f8c1ba27b8f59eeefa2e1ae62eed8c75", + "blockNumber": "0x52c", + "blockTimestamp": "0x6a5dfb5c", + "transactionHash": "0xf32fa95155f722c34245d4159ecba34e4a77c324cd3fe299aa04d5809c288256", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x10c3c873f1e42bb73dc392fde53cfd17f8c1ba27b8f59eeefa2e1ae62eed8c75", + "blockNumber": "0x52c", + "blockTimestamp": "0x6a5dfb5c", + "transactionHash": "0xf32fa95155f722c34245d4159ecba34e4a77c324cd3fe299aa04d5809c288256", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x03e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d27ab1a33e154b6d1f0b03982eea8a70bcd616e9aeea44b1256726e9ecc30ab78", + "blockHash": "0x98a5fb3c54f68dfe7ad8aaeb1c7d23f49e095e0a70d8ae86f4155e82d98dcc49", + "blockNumber": "0x52d", + "blockTimestamp": "0x6a5dfb5c", + "transactionHash": "0xa4c238fafb05cb0a637807125dcc0e5231a6da462b2a768eedc4aac12da97c76", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x98a5fb3c54f68dfe7ad8aaeb1c7d23f49e095e0a70d8ae86f4155e82d98dcc49", + "blockNumber": "0x52d", + "blockTimestamp": "0x6a5dfb5c", + "transactionHash": "0xa4c238fafb05cb0a637807125dcc0e5231a6da462b2a768eedc4aac12da97c76", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x27ab1a33e154b6d1f0b03982eea8a70bcd616e9aeea44b1256726e9ecc30ab782f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f88", + "blockHash": "0xb1d2aadce52978cb59f7f47e1f4c001b11d07c09a9536bca6a5320482a103007", + "blockNumber": "0x52f", + "blockTimestamp": "0x6a5dfb5d", + "transactionHash": "0xaf5ae35d6c552cd55a671fea1e1c4068a08df94ee757f8d54c8c390d2881022a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb1d2aadce52978cb59f7f47e1f4c001b11d07c09a9536bca6a5320482a103007", + "blockNumber": "0x52f", + "blockTimestamp": "0x6a5dfb5d", + "transactionHash": "0xaf5ae35d6c552cd55a671fea1e1c4068a08df94ee757f8d54c8c390d2881022a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f8874922cd75b71cb4576e5736f6f6efa45b1729d1711ea616357be3cf87f208910", + "blockHash": "0x5c6d3cd1f0559a407579a43a0832868ec404f6fffc6fc6803cdc3182ec595494", + "blockNumber": "0x530", + "blockTimestamp": "0x6a5dfb5d", + "transactionHash": "0xf19bf82339ebef5f0d49e6c35e82d431391782f63154de47b07a0dd11db07283", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5c6d3cd1f0559a407579a43a0832868ec404f6fffc6fc6803cdc3182ec595494", + "blockNumber": "0x530", + "blockTimestamp": "0x6a5dfb5d", + "transactionHash": "0xf19bf82339ebef5f0d49e6c35e82d431391782f63154de47b07a0dd11db07283", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x74922cd75b71cb4576e5736f6f6efa45b1729d1711ea616357be3cf87f2089104b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd77", + "blockHash": "0xb4faaa293f2ac4ff75f548873a4f595b72a36211f4a14b291dbe3bc8f2b84243", + "blockNumber": "0x532", + "blockTimestamp": "0x6a5dfb5e", + "transactionHash": "0xfd0fffc0da1cb3790b4fa0bceffe06e4b9f4e4281f93eefdf57176dad06fac7c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb4faaa293f2ac4ff75f548873a4f595b72a36211f4a14b291dbe3bc8f2b84243", + "blockNumber": "0x532", + "blockTimestamp": "0x6a5dfb5e", + "transactionHash": "0xfd0fffc0da1cb3790b4fa0bceffe06e4b9f4e4281f93eefdf57176dad06fac7c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x4b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd7771c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98b", + "blockHash": "0x6dfe41036f0b402665ea48ccdce922760a8d7590e6524784ba6b5744404e6b49", + "blockNumber": "0x533", + "blockTimestamp": "0x6a5dfb5f", + "transactionHash": "0x3a7b4aa47f7730a3ed369e90d5a74a766c7a36e5dfcc5bd4027ae888d05f7680", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b8cc000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6dfe41036f0b402665ea48ccdce922760a8d7590e6524784ba6b5744404e6b49", + "blockNumber": "0x533", + "blockTimestamp": "0x6a5dfb5f", + "transactionHash": "0x3a7b4aa47f7730a3ed369e90d5a74a766c7a36e5dfcc5bd4027ae888d05f7680", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0x71c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98bcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402", + "blockHash": "0x16c33a2fac838781e4a305497f40073e73bd3007bdb59e2cfa0f61ef012d7f20", + "blockNumber": "0x534", + "blockTimestamp": "0x6a5dfb5f", + "transactionHash": "0x5e485ae819d6c15daccc5784b3231169225f49888562186b4f04e48cd9f9337e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x16c33a2fac838781e4a305497f40073e73bd3007bdb59e2cfa0f61ef012d7f20", + "blockNumber": "0x534", + "blockTimestamp": "0x6a5dfb5f", + "transactionHash": "0x5e485ae819d6c15daccc5784b3231169225f49888562186b4f04e48cd9f9337e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402d2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b", + "blockHash": "0x7f1f9af9ae7f1a7eb688f607a56c1143782aca9ea71a62c5b348716558963238", + "blockNumber": "0x536", + "blockTimestamp": "0x6a5dfb60", + "transactionHash": "0x3c95758a9473961968413b6d074baa1b97fa6164c921285030218ff825ae7507", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7f1f9af9ae7f1a7eb688f607a56c1143782aca9ea71a62c5b348716558963238", + "blockNumber": "0x536", + "blockTimestamp": "0x6a5dfb60", + "transactionHash": "0x3c95758a9473961968413b6d074baa1b97fa6164c921285030218ff825ae7507", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972" + ], + "data": "0xd2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x7d01d3b9aecc5c09ec7bc859f081d2e25b7cfc3c4bf8a1a29dcee9b994e984a0", + "blockNumber": "0x538", + "blockTimestamp": "0x6a5dfb61", + "transactionHash": "0x3777e69c3f0f9c0c23df9b8d6469e9674372ca7bf4a590dfac62f40d15bab004", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7d01d3b9aecc5c09ec7bc859f081d2e25b7cfc3c4bf8a1a29dcee9b994e984a0", + "blockNumber": "0x538", + "blockTimestamp": "0x6a5dfb61", + "transactionHash": "0x3777e69c3f0f9c0c23df9b8d6469e9674372ca7bf4a590dfac62f40d15bab004", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972", + "0x000000000000000000000000f426647e099cce24defa48c4ffa0a7de22ccc73b" + ], + "data": "0x", + "blockHash": "0xd92bf49b5e49ae3f34565e1996cdae3f0779570cdc2625a2f4eeb0d2c7929a7b", + "blockNumber": "0x539", + "blockTimestamp": "0x6a5dfb61", + "transactionHash": "0x51b371a9b6fa522e8e015c9a9baa3385c43744a885d7f80906c357c03db37159", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000266ab800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd92bf49b5e49ae3f34565e1996cdae3f0779570cdc2625a2f4eeb0d2c7929a7b", + "blockNumber": "0x539", + "blockTimestamp": "0x6a5dfb61", + "transactionHash": "0x51b371a9b6fa522e8e015c9a9baa3385c43744a885d7f80906c357c03db37159", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x44cad2e9423059f845ca57ac48a4499a35efb7b42b35c66842a9db6eb2a5536301c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x2e1fef980fddaa3e1fccee7f9611d3c87cad9d3d1a1ddaac5e70dd0983b02f91", + "blockNumber": "0x53b", + "blockTimestamp": "0x6a5dfb62", + "transactionHash": "0xfee4dc2c81459cc58b41a2e2e5459a52a9822d392a5b1d770014140d6fbd0b04", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906" + ], + "data": "0x70843900ffa0b85f86f7b63a38c6dc07bd3a37f592597412e7f8d58bd35ae39f0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8ca1df6113610d80f1e330e41fd929639f7c978576b198053e6db2d8bbf66e26", + "blockNumber": "0x53c", + "blockTimestamp": "0x6a5dfb62", + "transactionHash": "0xb4b6c4bf58b82375c31c421b1674fea360b5bb1e72fc98fa6aaba1509e24d097", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08", + "0x44cad2e9423059f845ca57ac48a4499a35efb7b42b35c66842a9db6eb2a55363", + "0x70843900ffa0b85f86f7b63a38c6dc07bd3a37f592597412e7f8d58bd35ae39f" + ], + "data": "0x8b19f1d76e3c14a54339466f754f5bca310ff366a9fb4dfb617371ba9d7c95ec", + "blockHash": "0x8ca1df6113610d80f1e330e41fd929639f7c978576b198053e6db2d8bbf66e26", + "blockNumber": "0x53c", + "blockTimestamp": "0x6a5dfb62", + "transactionHash": "0xb4b6c4bf58b82375c31c421b1674fea360b5bb1e72fc98fa6aaba1509e24d097", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x8b19f1d76e3c14a54339466f754f5bca310ff366a9fb4dfb617371ba9d7c95ec4d112390a2069e0781a6a30b20a075164136024b1e31e5384fbb74010de55e10", + "blockHash": "0x08771684a49f81805166c33f4b40e1e94537df9b9237c0629fed9257600652c3", + "blockNumber": "0x53e", + "blockTimestamp": "0x6a5dfb63", + "transactionHash": "0x7783fe42d67cb7ce652e54a43c83606523b370643aa722df1ae2b1c5fae6cb8a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x08771684a49f81805166c33f4b40e1e94537df9b9237c0629fed9257600652c3", + "blockNumber": "0x53e", + "blockTimestamp": "0x6a5dfb63", + "transactionHash": "0x7783fe42d67cb7ce652e54a43c83606523b370643aa722df1ae2b1c5fae6cb8a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x4d112390a2069e0781a6a30b20a075164136024b1e31e5384fbb74010de55e10129c1a3494389365368c7288e3ced0f7d8d4468a098b4885a258df0f881e4d15", + "blockHash": "0x51a8133fa84a7be5e0434b26ec877d045ae68470a58e6fb55a47b6d6a9eaf6b7", + "blockNumber": "0x53f", + "blockTimestamp": "0x6a5dfb64", + "transactionHash": "0x623c77a7801b414b0002f8dfea60666e4b75aa7fefde027f95a0675686f092f7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x51a8133fa84a7be5e0434b26ec877d045ae68470a58e6fb55a47b6d6a9eaf6b7", + "blockNumber": "0x53f", + "blockTimestamp": "0x6a5dfb64", + "transactionHash": "0x623c77a7801b414b0002f8dfea60666e4b75aa7fefde027f95a0675686f092f7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x129c1a3494389365368c7288e3ced0f7d8d4468a098b4885a258df0f881e4d159779995ff6b324458cce7ed27c799c5a645dd32096a171ca69ae11c54a8f6639", + "blockHash": "0xb7d4de6c0256e7c97436bbdb07d8b595e4a0a0b6ed713057706897f1ecb3d27f", + "blockNumber": "0x541", + "blockTimestamp": "0x6a5dfb65", + "transactionHash": "0x01ae9c7c64eb175d39e79cc7d8dd7991e3cd716764abc67300130af28907fbf8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb7d4de6c0256e7c97436bbdb07d8b595e4a0a0b6ed713057706897f1ecb3d27f", + "blockNumber": "0x541", + "blockTimestamp": "0x6a5dfb65", + "transactionHash": "0x01ae9c7c64eb175d39e79cc7d8dd7991e3cd716764abc67300130af28907fbf8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x9779995ff6b324458cce7ed27c799c5a645dd32096a171ca69ae11c54a8f6639d9b32427bec25cacd2b713fa3f5a6d8fc8e6c9d4a33b6c3cac90a1857a73d1e5", + "blockHash": "0x00a0699c14f8009edeb3c9ede3b9923025c9fb1c194bbfbda4520f972a8ffacb", + "blockNumber": "0x542", + "blockTimestamp": "0x6a5dfb65", + "transactionHash": "0x95b0c79b370e106ef0ef1a9683c901bebc30c1f32107a6123c15357f695cae7f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x00a0699c14f8009edeb3c9ede3b9923025c9fb1c194bbfbda4520f972a8ffacb", + "blockNumber": "0x542", + "blockTimestamp": "0x6a5dfb65", + "transactionHash": "0x95b0c79b370e106ef0ef1a9683c901bebc30c1f32107a6123c15357f695cae7f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0xd9b32427bec25cacd2b713fa3f5a6d8fc8e6c9d4a33b6c3cac90a1857a73d1e59f161eeb7f79083bf5289f0892e0f8a3d11fc73275a1ac8e26df840831bf2933", + "blockHash": "0xfd544e19b60a89724c176f9cda38157a246c1e231193c10755b52b5f8e314147", + "blockNumber": "0x544", + "blockTimestamp": "0x6a5dfb66", + "transactionHash": "0x8203a708bf6a78b40eb13a4040f6f78324756afe8809e4331355363e1f7c626c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfd544e19b60a89724c176f9cda38157a246c1e231193c10755b52b5f8e314147", + "blockNumber": "0x544", + "blockTimestamp": "0x6a5dfb66", + "transactionHash": "0x8203a708bf6a78b40eb13a4040f6f78324756afe8809e4331355363e1f7c626c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x9f161eeb7f79083bf5289f0892e0f8a3d11fc73275a1ac8e26df840831bf293331734d04b03a63cf6e67550413631e3a7cb00df3a44469258e57e950de0be6f4", + "blockHash": "0x040815491cbd34529a8ec897ac5fe41faa33c1f6ef5d04101a1ebf16c0d687be", + "blockNumber": "0x545", + "blockTimestamp": "0x6a5dfb67", + "transactionHash": "0x3d48d44d834c8d55ec817c3654977ce3f91542a77ad392234278158d5f82442c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x040815491cbd34529a8ec897ac5fe41faa33c1f6ef5d04101a1ebf16c0d687be", + "blockNumber": "0x545", + "blockTimestamp": "0x6a5dfb67", + "transactionHash": "0x3d48d44d834c8d55ec817c3654977ce3f91542a77ad392234278158d5f82442c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x31734d04b03a63cf6e67550413631e3a7cb00df3a44469258e57e950de0be6f407e0b23aae4d00119dc62cbb2beace8e1d8bc5619bc400ff9723792df5c57d2e", + "blockHash": "0xc120f661ab6819a18029dbd82549440df518b938eacab89844805e6fbe0a922e", + "blockNumber": "0x547", + "blockTimestamp": "0x6a5dfb68", + "transactionHash": "0xb17a202e1b45ad23081a074e8d8808d1d323d95d5b22f975e8d8ebb79ce86213", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc120f661ab6819a18029dbd82549440df518b938eacab89844805e6fbe0a922e", + "blockNumber": "0x547", + "blockTimestamp": "0x6a5dfb68", + "transactionHash": "0xb17a202e1b45ad23081a074e8d8808d1d323d95d5b22f975e8d8ebb79ce86213", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x07e0b23aae4d00119dc62cbb2beace8e1d8bc5619bc400ff9723792df5c57d2e3dfe62562975fa6411c4f1413927bdefd0218d0ac81245a0be91db483283b112", + "blockHash": "0x5c35aac62943afbe0975a15a3ca2409dabf6f68605d03f4269bbd2963bc27bfa", + "blockNumber": "0x548", + "blockTimestamp": "0x6a5dfb68", + "transactionHash": "0xa3b1643b9a9d89f3b03eac4501a6dce1ccf81039486dbaac2b477a757f707b47", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5c35aac62943afbe0975a15a3ca2409dabf6f68605d03f4269bbd2963bc27bfa", + "blockNumber": "0x548", + "blockTimestamp": "0x6a5dfb68", + "transactionHash": "0xa3b1643b9a9d89f3b03eac4501a6dce1ccf81039486dbaac2b477a757f707b47", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x3dfe62562975fa6411c4f1413927bdefd0218d0ac81245a0be91db483283b11222bb7c8e748c818e07d70fa3ed53961eb2b6521c5a446911c3ea87eb16ece59c", + "blockHash": "0x477ffedaa657b9b07ab346bb32ccc411adf9fef417cf670c5573f60023bf43c0", + "blockNumber": "0x54a", + "blockTimestamp": "0x6a5dfb69", + "transactionHash": "0x97e9d0f423f4cfb5702d8ebe147d28929ab97b47e46006d016688ab00450f757", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x477ffedaa657b9b07ab346bb32ccc411adf9fef417cf670c5573f60023bf43c0", + "blockNumber": "0x54a", + "blockTimestamp": "0x6a5dfb69", + "transactionHash": "0x97e9d0f423f4cfb5702d8ebe147d28929ab97b47e46006d016688ab00450f757", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x22bb7c8e748c818e07d70fa3ed53961eb2b6521c5a446911c3ea87eb16ece59cd248fccb211484f1b51fdca61a3f9c0515f2c463b8013b3042d91513da116b11", + "blockHash": "0x0391cf51b81e3767053eb47814c9a03d419f09aeb361b818e092c167199c4215", + "blockNumber": "0x54b", + "blockTimestamp": "0x6a5dfb6a", + "transactionHash": "0xced5d6a3a8b2aa7576dcdcece2e21c8af1682aa4917d8520f48b3d9bbfe99e57", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0391cf51b81e3767053eb47814c9a03d419f09aeb361b818e092c167199c4215", + "blockNumber": "0x54b", + "blockTimestamp": "0x6a5dfb6a", + "transactionHash": "0xced5d6a3a8b2aa7576dcdcece2e21c8af1682aa4917d8520f48b3d9bbfe99e57", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0xd248fccb211484f1b51fdca61a3f9c0515f2c463b8013b3042d91513da116b11b5a4b9588efc763331f8dd4fd5b57e26124fa91e872b7afbcfcdec9c28d7bddb", + "blockHash": "0x9c805c2a4482e5c54df8243226b37a54caf2247c753caef22585b8032e2a6cf7", + "blockNumber": "0x54d", + "blockTimestamp": "0x6a5dfb6a", + "transactionHash": "0xf8aab3275454368ade5b73b5e2ad820336337fcb54c2e19db186efc85955a35f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9c805c2a4482e5c54df8243226b37a54caf2247c753caef22585b8032e2a6cf7", + "blockNumber": "0x54d", + "blockTimestamp": "0x6a5dfb6a", + "transactionHash": "0xf8aab3275454368ade5b73b5e2ad820336337fcb54c2e19db186efc85955a35f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0xb5a4b9588efc763331f8dd4fd5b57e26124fa91e872b7afbcfcdec9c28d7bddb4301c46cbb6cdbd004cdb8a94b76f1c235aba51db1eea8eca41024b98d2b9de1", + "blockHash": "0xd2e18f6ab00ee153454daf99fe27187c6b20007c103e473f4d2ad6825d8d8ff7", + "blockNumber": "0x54e", + "blockTimestamp": "0x6a5dfb6b", + "transactionHash": "0xfcacba26dec2a64fdc2e46300700c7b3f5506fbb371f0b0282dc72e22c22dc70", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd2e18f6ab00ee153454daf99fe27187c6b20007c103e473f4d2ad6825d8d8ff7", + "blockNumber": "0x54e", + "blockTimestamp": "0x6a5dfb6b", + "transactionHash": "0xfcacba26dec2a64fdc2e46300700c7b3f5506fbb371f0b0282dc72e22c22dc70", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x4301c46cbb6cdbd004cdb8a94b76f1c235aba51db1eea8eca41024b98d2b9de1808fe019d787760037e7c5502cfe11b83ab0324f218c6bc2744d81ffe6c713cf", + "blockHash": "0x03867ae03f91c7429eaf49288d660aaef093adcfe7bc119ac1f1dc4ffd803207", + "blockNumber": "0x550", + "blockTimestamp": "0x6a5dfb6c", + "transactionHash": "0xb109afe20ce8100181366f9f30bb91450d43efaf56eb2fa5d66dd294271bd7d3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x03867ae03f91c7429eaf49288d660aaef093adcfe7bc119ac1f1dc4ffd803207", + "blockNumber": "0x550", + "blockTimestamp": "0x6a5dfb6c", + "transactionHash": "0xb109afe20ce8100181366f9f30bb91450d43efaf56eb2fa5d66dd294271bd7d3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x808fe019d787760037e7c5502cfe11b83ab0324f218c6bc2744d81ffe6c713cfeaba47575889433dc8e6e5a9ceea3e0fafab705b719b7c1d5c80de3203b56af8", + "blockHash": "0xe014cdb53f78edc7b242eb2a6157618d3e9948b9d89152a4dfc6862dcc7cf9de", + "blockNumber": "0x551", + "blockTimestamp": "0x6a5dfb6d", + "transactionHash": "0xa6f0504075c1963d93a87e3df84aa7e8d49cdcd6a24b6bd82b20ecd46f4272be", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe014cdb53f78edc7b242eb2a6157618d3e9948b9d89152a4dfc6862dcc7cf9de", + "blockNumber": "0x551", + "blockTimestamp": "0x6a5dfb6d", + "transactionHash": "0xa6f0504075c1963d93a87e3df84aa7e8d49cdcd6a24b6bd82b20ecd46f4272be", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0xeaba47575889433dc8e6e5a9ceea3e0fafab705b719b7c1d5c80de3203b56af8025a36c32ce32ba22736562af4b1a226eaef2541bbae150892c890c1f7728d66", + "blockHash": "0xe448360abd76d61f1e64519a446ea262a8fcd1c98517e02c5370f6ead2670f0d", + "blockNumber": "0x553", + "blockTimestamp": "0x6a5dfb6d", + "transactionHash": "0xbf29eb26e8fd38194adc59ade911b42612f660a5b7dd2ebb3143b8fe26607950", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe448360abd76d61f1e64519a446ea262a8fcd1c98517e02c5370f6ead2670f0d", + "blockNumber": "0x553", + "blockTimestamp": "0x6a5dfb6d", + "transactionHash": "0xbf29eb26e8fd38194adc59ade911b42612f660a5b7dd2ebb3143b8fe26607950", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08" + ], + "data": "0x025a36c32ce32ba22736562af4b1a226eaef2541bbae150892c890c1f7728d6601c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xd63387c903b6198068980197ebda3e03e3264389a2b508f4be2d5a547b67e686", + "blockNumber": "0x554", + "blockTimestamp": "0x6a5dfb6e", + "transactionHash": "0x6b929e6a6064051df629349ad863daa49a05c61d706e69ca7a54b95fafd8ccc1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd63387c903b6198068980197ebda3e03e3264389a2b508f4be2d5a547b67e686", + "blockNumber": "0x554", + "blockTimestamp": "0x6a5dfb6e", + "transactionHash": "0x6b929e6a6064051df629349ad863daa49a05c61d706e69ca7a54b95fafd8ccc1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08", + "0x000000000000000000000000a4f2ef80bea74736afd56ab3a15b4b7c80fabfe0" + ], + "data": "0x", + "blockHash": "0x1c17130f27d3fe60c3f7fe562398d624166d9c05c1fcccf07ab53a7b7e9d9945", + "blockNumber": "0x556", + "blockTimestamp": "0x6a5dfb6f", + "transactionHash": "0xc5fb8344948933198949d43f391bb2caf53a254039a75649a00fcba674fd5ea9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000002794b800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1c17130f27d3fe60c3f7fe562398d624166d9c05c1fcccf07ab53a7b7e9d9945", + "blockNumber": "0x556", + "blockTimestamp": "0x6a5dfb6f", + "transactionHash": "0xc5fb8344948933198949d43f391bb2caf53a254039a75649a00fcba674fd5ea9", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906" + ], + "data": "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe6831ad36b814fb5f73869d7e99f8fd4b875abdac7ffcef5f4c63055875231ad", + "blockNumber": "0x557", + "blockTimestamp": "0x6a5dfb70", + "transactionHash": "0xf90a9f0b843f08e6954a872170670cbf5442377140de4b5848cfab5d198d2997", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x10944061f1e115200a22220482f75b33585aeab9c9d4042b6cd4591e82bdf47c", + "blockNumber": "0x559", + "blockTimestamp": "0x6a5dfb70", + "transactionHash": "0x63a931df723e75ed8bdbaae62f24d084bd7d21f0773a672010d9c58f7f11ed30", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a", + "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e", + "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef" + ], + "data": "0x212283bc1af796a34213eb18e98a113d32f9ea8b4944a5af675b7670c2e5d0e7", + "blockHash": "0x10944061f1e115200a22220482f75b33585aeab9c9d4042b6cd4591e82bdf47c", + "blockNumber": "0x559", + "blockTimestamp": "0x6a5dfb70", + "transactionHash": "0x63a931df723e75ed8bdbaae62f24d084bd7d21f0773a672010d9c58f7f11ed30", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x212283bc1af796a34213eb18e98a113d32f9ea8b4944a5af675b7670c2e5d0e7e710743051fe19bd9c88126f970e6f7272855940f855e3ea04ad415c27486fe5", + "blockHash": "0x13bd1a780cac04d36e31a9c0827346dac2734fef325496fb46d89e9e4c053dbb", + "blockNumber": "0x55a", + "blockTimestamp": "0x6a5dfb71", + "transactionHash": "0x54b0f690f4df312131594aba432b0901a72ebd9d09d06fde9dd392a35de2fae5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x13bd1a780cac04d36e31a9c0827346dac2734fef325496fb46d89e9e4c053dbb", + "blockNumber": "0x55a", + "blockTimestamp": "0x6a5dfb71", + "transactionHash": "0x54b0f690f4df312131594aba432b0901a72ebd9d09d06fde9dd392a35de2fae5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xbf674b2dacad2be0bd35165fe8d338711a7788f77df3914033d79c34373f169bb3718afeb8182524f7a0af7f95ceb5ccb5e5b9a35a3dbcb52259143e606783c9", + "blockHash": "0xd3db9fb30883a82365ce75081456b0b79347c81fbb1234610732e93196f398fa", + "blockNumber": "0x55c", + "blockTimestamp": "0x6a5dfb71", + "transactionHash": "0xf53417fba3da1e4d595658e7a65133c77449a6cdbe42d076aa7754da779de246", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd3db9fb30883a82365ce75081456b0b79347c81fbb1234610732e93196f398fa", + "blockNumber": "0x55c", + "blockTimestamp": "0x6a5dfb71", + "transactionHash": "0xf53417fba3da1e4d595658e7a65133c77449a6cdbe42d076aa7754da779de246", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xb3718afeb8182524f7a0af7f95ceb5ccb5e5b9a35a3dbcb52259143e606783c958090a5b58f6621d5a9a4c1e9fae57cc435296683419fbcb3d40e240bd3f63f9", + "blockHash": "0x0a5dd8af50e09fce16b937e8d292269db9798c59e3665b3856b6b48a41909d76", + "blockNumber": "0x55d", + "blockTimestamp": "0x6a5dfb73", + "transactionHash": "0x58fae650ee0b6295d15b375a8dd82d6d0278f732128cc7f4c41927ceadad1a38", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0a5dd8af50e09fce16b937e8d292269db9798c59e3665b3856b6b48a41909d76", + "blockNumber": "0x55d", + "blockTimestamp": "0x6a5dfb73", + "transactionHash": "0x58fae650ee0b6295d15b375a8dd82d6d0278f732128cc7f4c41927ceadad1a38", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xa4bd461d3dcbfecd4c1505f5d94f5a04e1713a777a254231e2602d9daf34e9a8f3ea743772203c1c29d75e901b516c2ec36458a85ca177734239283df828b85f", + "blockHash": "0x2c9936b3fcdc9d71cca2ea8f6755ab82cdcdbb932d8f540cf2ed07c68678f020", + "blockNumber": "0x55f", + "blockTimestamp": "0x6a5dfb73", + "transactionHash": "0x297443701865a0ea2ebeb276c122ce105c15f7ed7c6ddf3c3f4a59a973ab2cc2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2c9936b3fcdc9d71cca2ea8f6755ab82cdcdbb932d8f540cf2ed07c68678f020", + "blockNumber": "0x55f", + "blockTimestamp": "0x6a5dfb73", + "transactionHash": "0x297443701865a0ea2ebeb276c122ce105c15f7ed7c6ddf3c3f4a59a973ab2cc2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf3ea743772203c1c29d75e901b516c2ec36458a85ca177734239283df828b85f0a58c8a1e0f70607f5c5155ed30d5cec3f6f3b349a17a89bc5b10a4fc8dde221", + "blockHash": "0x5b01dd468bca885792d9d2734b8a9070ddbd7af17fde6b19c121157589c394f4", + "blockNumber": "0x560", + "blockTimestamp": "0x6a5dfb74", + "transactionHash": "0xe132aee3bb41ac40217b41ee13ca74fee5a7af0c033bd531c2966db9b87762a3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5b01dd468bca885792d9d2734b8a9070ddbd7af17fde6b19c121157589c394f4", + "blockNumber": "0x560", + "blockTimestamp": "0x6a5dfb74", + "transactionHash": "0xe132aee3bb41ac40217b41ee13ca74fee5a7af0c033bd531c2966db9b87762a3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x1a9ac08428ec806e51ca8f71379b1d2a4ce6a4138d70c59fb374736297b4ed528490c30cba5c743759ee0ff1e33893e65dbaaa7b5b4686a90ec36b6b62da638d", + "blockHash": "0x1084335fb77c35a284746896aa990edc1e4ed22546a059029b449810972aed45", + "blockNumber": "0x562", + "blockTimestamp": "0x6a5dfbc1", + "transactionHash": "0x837b58c4d4dadffa40d83a92cce0bd4adcdc7e41e4c6fc2aee5a52d2921b64ff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1084335fb77c35a284746896aa990edc1e4ed22546a059029b449810972aed45", + "blockNumber": "0x562", + "blockTimestamp": "0x6a5dfbc1", + "transactionHash": "0x837b58c4d4dadffa40d83a92cce0bd4adcdc7e41e4c6fc2aee5a52d2921b64ff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x8490c30cba5c743759ee0ff1e33893e65dbaaa7b5b4686a90ec36b6b62da638d9173f94f2fb97eef56ba6fc43925899cc7aabc7daf485ef14f5de621ac79055d", + "blockHash": "0x1f581e0a5c1298043dfc6b91cf248b5181a95d007fc5a86c459223776b8fb343", + "blockNumber": "0x563", + "blockTimestamp": "0x6a5dfbc3", + "transactionHash": "0xc56ec6f7277b170e5a227a2c76488cb17498443a80a73b503d081fe2c1d4a91f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1f581e0a5c1298043dfc6b91cf248b5181a95d007fc5a86c459223776b8fb343", + "blockNumber": "0x563", + "blockTimestamp": "0x6a5dfbc3", + "transactionHash": "0xc56ec6f7277b170e5a227a2c76488cb17498443a80a73b503d081fe2c1d4a91f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x159230bfb0324ac9328207a68321ad4e28cee2025735dd5f0e27cddc9ee018e2489c6b428f50828ff1e54dc32b80ec6793da8e775c5f308d98a176d3f3af40ae", + "blockHash": "0x7ac5d88b47ab8671f82720ee0c43270b6ab4ec553381fb6eafeb41fc6014818b", + "blockNumber": "0x565", + "blockTimestamp": "0x6a5dfbc3", + "transactionHash": "0x368ce89f978a3bbedc6591e7d9f4f56e6fb1644b7be865b9965d56ab394c92fd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7ac5d88b47ab8671f82720ee0c43270b6ab4ec553381fb6eafeb41fc6014818b", + "blockNumber": "0x565", + "blockTimestamp": "0x6a5dfbc3", + "transactionHash": "0x368ce89f978a3bbedc6591e7d9f4f56e6fb1644b7be865b9965d56ab394c92fd", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xc6f1c02a188ce786f5a62481f14c189b8a6c36756a47c5d0c687f81f9a899f507edd771879c5c45249a814ae964960b87b3bd6df7da0b20e177b80a3f3959c2a", + "blockHash": "0x22309f66fcffbcbdd6f02d51f36eef82fce92ccb73daf9622c4f43b774e10939", + "blockNumber": "0x566", + "blockTimestamp": "0x6a5dfbc4", + "transactionHash": "0x1a5f4824399421ed09ea0298520cf23895efd66ea34a6d7b9602f2739fe2ced7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x22309f66fcffbcbdd6f02d51f36eef82fce92ccb73daf9622c4f43b774e10939", + "blockNumber": "0x566", + "blockTimestamp": "0x6a5dfbc4", + "transactionHash": "0x1a5f4824399421ed09ea0298520cf23895efd66ea34a6d7b9602f2739fe2ced7", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x55dd2594d4377713cbeeae46fdd8cc1addf9b635447b5ccb86dc84acd3ddbe2cac1baafec3ed4ae09996488a93ed34cb2149b37362f0824101017b9f0fe144d7", + "blockHash": "0x3d0106acea6fa7a39bb28f401442a7173387c0f407518b545ba37b3df17e6131", + "blockNumber": "0x568", + "blockTimestamp": "0x6a5dfbc5", + "transactionHash": "0x77504914078d294bc63b315c89751737df7286e2038c0365be3bcd8a9ffca879", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3d0106acea6fa7a39bb28f401442a7173387c0f407518b545ba37b3df17e6131", + "blockNumber": "0x568", + "blockTimestamp": "0x6a5dfbc5", + "transactionHash": "0x77504914078d294bc63b315c89751737df7286e2038c0365be3bcd8a9ffca879", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x6b853b3bcbee314f2bf4086391e1a1988060af063328df9f95c6d9566f9fb3c729fb08620d76ec5be88f9abda6a715d2477a4f49f4203a9711532f929c065510", + "blockHash": "0xee13ddb38c1f75f18caa48e59d40a1b5a3aea2b33876b88c89b860819634d365", + "blockNumber": "0x569", + "blockTimestamp": "0x6a5dfbc6", + "transactionHash": "0xb5a8b700803349a7af431c72a2a59be9028fd41e2ee0287b6430dd82b762f457", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xee13ddb38c1f75f18caa48e59d40a1b5a3aea2b33876b88c89b860819634d365", + "blockNumber": "0x569", + "blockTimestamp": "0x6a5dfbc6", + "transactionHash": "0xb5a8b700803349a7af431c72a2a59be9028fd41e2ee0287b6430dd82b762f457", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf9d31d7058d2acc7c04cf77e9be54da518ae1e3ebab70d8b9e76e08397a21bbcd439bc4ed00022367503e745162a2f78d764e4ca8ce0022add34d782f326521d", + "blockHash": "0x6069c907335bf5e1e68801484fabac96edfb4de077dca304b6ae686816bd59a8", + "blockNumber": "0x56b", + "blockTimestamp": "0x6a5dfbc6", + "transactionHash": "0xec2b64b78fddecca42efd480ce1b0af9cd2c0a52df6a079565a3e8b63aef15ef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x6069c907335bf5e1e68801484fabac96edfb4de077dca304b6ae686816bd59a8", + "blockNumber": "0x56b", + "blockTimestamp": "0x6a5dfbc6", + "transactionHash": "0xec2b64b78fddecca42efd480ce1b0af9cd2c0a52df6a079565a3e8b63aef15ef", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x5179b31126a75ac5c6aaad921a1c2c4915d37b319f6f4aa834e19fcde153b13e55c1811bab335ca8c37318770b4f739bc8d4632bd0e808fe88c72fbd579b2225", + "blockHash": "0x5fc0d41920c2f0b76ef702db6a345de040cbf52bc9c5e0d6925e17b24e963cd9", + "blockNumber": "0x56c", + "blockTimestamp": "0x6a5dfbc8", + "transactionHash": "0x849ec2c80ae71743477381f7d35745058e0564ed01f3a41db8e2f84154c1768a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5fc0d41920c2f0b76ef702db6a345de040cbf52bc9c5e0d6925e17b24e963cd9", + "blockNumber": "0x56c", + "blockTimestamp": "0x6a5dfbc8", + "transactionHash": "0x849ec2c80ae71743477381f7d35745058e0564ed01f3a41db8e2f84154c1768a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x50e14e3a99ccc49ec20f9ad9f2da03f46d437124e4384d3b367da45885fb0f2414be422e2b39e32a5ca093be40341bd2de5d2365f3b81e47e72a60fb882a537e", + "blockHash": "0x492d255ab4c0eaab37ace40f8f8583d1fa9d1432aea28354aa2bb6d1e4a80063", + "blockNumber": "0x56e", + "blockTimestamp": "0x6a5dfbc8", + "transactionHash": "0xa492fb3e5200761adeaff6cda3fbbb61ed251fa780c877e0ab0ca521af794f5a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x492d255ab4c0eaab37ace40f8f8583d1fa9d1432aea28354aa2bb6d1e4a80063", + "blockNumber": "0x56e", + "blockTimestamp": "0x6a5dfbc8", + "transactionHash": "0xa492fb3e5200761adeaff6cda3fbbb61ed251fa780c877e0ab0ca521af794f5a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x8cbdb2bff812280b22c86c88904be0872214f659292fd6a40cd62cc43102c171d5a77365c6b697e1c48d2d77ec12b60a09b0fe559b09622e0a2576a40468c228", + "blockHash": "0x5a1b104cd012691372a60b9debfad972b8bb62415f65ba10a37de61edbe04e53", + "blockNumber": "0x56f", + "blockTimestamp": "0x6a5dfbc9", + "transactionHash": "0xf792faab40a3cf4765c41187bc855bdf374ab9b8b1993d56e74ef10fb4a4addb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5a1b104cd012691372a60b9debfad972b8bb62415f65ba10a37de61edbe04e53", + "blockNumber": "0x56f", + "blockTimestamp": "0x6a5dfbc9", + "transactionHash": "0xf792faab40a3cf4765c41187bc855bdf374ab9b8b1993d56e74ef10fb4a4addb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x46e91db90736fff97c3a74d75c09de0e1d9f10d6ebe91e173aff38bd384392fa6ff2c2c0cd789e6b5928356f19a99b7a67b813b54e30c1d8bad7a305efb589a3", + "blockHash": "0xf5f5172adbe960a0579c7a571731a0f57eef71e87c4384b0cdd7d3784eefdd9f", + "blockNumber": "0x571", + "blockTimestamp": "0x6a5dfbc9", + "transactionHash": "0x4e94b8b3917592d5d80af5fccf9c53171da9401d986eb2a06a15f7ee11a50493", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf5f5172adbe960a0579c7a571731a0f57eef71e87c4384b0cdd7d3784eefdd9f", + "blockNumber": "0x571", + "blockTimestamp": "0x6a5dfbc9", + "transactionHash": "0x4e94b8b3917592d5d80af5fccf9c53171da9401d986eb2a06a15f7ee11a50493", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xfaf4d4d5dcac5a767078ba8273fb65a0de6387ffc1ee2b565229cbf03b3c73201a85dc955df5272e23d3bbe0419c618cf3958a2a5d9fa8c50233493170fb764b", + "blockHash": "0xca0eb7bf2f93d4a85ec418f68f56311ba4e9cd5360d79c36a6345dfac2f218b4", + "blockNumber": "0x572", + "blockTimestamp": "0x6a5dfbcb", + "transactionHash": "0x69f92996320866da4f53a96b52aad860f68a07bc6dcda88ee0bd102e33fd5f89", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xca0eb7bf2f93d4a85ec418f68f56311ba4e9cd5360d79c36a6345dfac2f218b4", + "blockNumber": "0x572", + "blockTimestamp": "0x6a5dfbcb", + "transactionHash": "0x69f92996320866da4f53a96b52aad860f68a07bc6dcda88ee0bd102e33fd5f89", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x523dd5a2aa53d58a0783f7d387a563ac28f1ed69d4d1faacc38faaa28b17598c599a9971be62e9b3c482a690c95012ac2739841ef6cf3e87872d6b63d29c04c8", + "blockHash": "0x064579d4dfe5daf1ecf592162da72b4326f301107da280772d77368e3a3571bb", + "blockNumber": "0x574", + "blockTimestamp": "0x6a5dfbcb", + "transactionHash": "0x48c080467d0c6f9bb8d158b8256de8c37f5ee44d6fa64d951f190a9691dbb247", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x064579d4dfe5daf1ecf592162da72b4326f301107da280772d77368e3a3571bb", + "blockNumber": "0x574", + "blockTimestamp": "0x6a5dfbcb", + "transactionHash": "0x48c080467d0c6f9bb8d158b8256de8c37f5ee44d6fa64d951f190a9691dbb247", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xf689486fd7025ee0c4a33c1e7ec310f0810e51251ab7197644f344cabf2d67d9fa0942ccf3b9486e854eb4e794e0ef4c8b3691a1a1069ed55b901bc9378aaafc", + "blockHash": "0x623dbe1959574620bd7ab78888d2dd13c2422849fbdec4dc26ceee35e112c9b6", + "blockNumber": "0x575", + "blockTimestamp": "0x6a5dfbcd", + "transactionHash": "0xc316471790129b06667a5a57ab6025d280ac3d1e3410d9001930be2f256dab38", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x623dbe1959574620bd7ab78888d2dd13c2422849fbdec4dc26ceee35e112c9b6", + "blockNumber": "0x575", + "blockTimestamp": "0x6a5dfbcd", + "transactionHash": "0xc316471790129b06667a5a57ab6025d280ac3d1e3410d9001930be2f256dab38", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xea0badf5a5a158370316a3bbc508cef75a4a216a97b18f36de50036bea2ff2060eb28f62864a84dcdd476d70df8fde77539d8c9cddd912c3948b91fb0813d980", + "blockHash": "0x734c093229a32b9fb53efc6e70fc7e512339c1682120d6a6b53ad89e8c191933", + "blockNumber": "0x577", + "blockTimestamp": "0x6a5dfbce", + "transactionHash": "0x3c79c13ba4e6dc4def8b2cb12f9413e118a22c9509a66e9088068686a8663301", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x734c093229a32b9fb53efc6e70fc7e512339c1682120d6a6b53ad89e8c191933", + "blockNumber": "0x577", + "blockTimestamp": "0x6a5dfbce", + "transactionHash": "0x3c79c13ba4e6dc4def8b2cb12f9413e118a22c9509a66e9088068686a8663301", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x707df0ef49e292d02e43cb33e0b203c1dee169144f82f532c5f7c67604e4e4eac8089ccd49d60350a5df7c2038f1a3d67134372dd36662a85bb71a9401a553e9", + "blockHash": "0x942c25c76828d622d65aaca3bc2a400c10a09571b6b7762d75732153ca5fe894", + "blockNumber": "0x578", + "blockTimestamp": "0x6a5dfbce", + "transactionHash": "0xf93d5e53819d84b4a3e3238dd1c2de55fd331fa092051f2ff637031750a5ac3d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x942c25c76828d622d65aaca3bc2a400c10a09571b6b7762d75732153ca5fe894", + "blockNumber": "0x578", + "blockTimestamp": "0x6a5dfbce", + "transactionHash": "0xf93d5e53819d84b4a3e3238dd1c2de55fd331fa092051f2ff637031750a5ac3d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xa65a9e3c95a7d90d561607071acad31f2aeaaa081840aa56599507e1d5c7ef193c00790459123e1fb955ad8a3acd051281426293d4ada112b557412fd865ab6a", + "blockHash": "0x7c0e7c91bfc25bcb551029147093c18e341d3c22e044fa907e70235545f82b13", + "blockNumber": "0x57a", + "blockTimestamp": "0x6a5dfbcf", + "transactionHash": "0x23fc68f64bc5c1aacf7515cdd7c9cfe08659988a1f5fc9a8b51937e176147a69", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7c0e7c91bfc25bcb551029147093c18e341d3c22e044fa907e70235545f82b13", + "blockNumber": "0x57a", + "blockTimestamp": "0x6a5dfbcf", + "transactionHash": "0x23fc68f64bc5c1aacf7515cdd7c9cfe08659988a1f5fc9a8b51937e176147a69", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x230a24ac4fe7a1a36215a300ea61f491f5a7bfa7896aa6afc73eda7d0908607647e456b343f32ee1d9b2408cac97e9dad6d63d6dde6bdf33eb7708be795ff5af", + "blockHash": "0xf941a7917cc581ee7733c0548b12062424c93bd8a423f16aa9a55d7a378497bf", + "blockNumber": "0x57b", + "blockTimestamp": "0x6a5dfbd0", + "transactionHash": "0xd47804f213221817dbb4653289499de09a1ab6e05d2a9ff587efae6ac71d1bf0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf941a7917cc581ee7733c0548b12062424c93bd8a423f16aa9a55d7a378497bf", + "blockNumber": "0x57b", + "blockTimestamp": "0x6a5dfbd0", + "transactionHash": "0xd47804f213221817dbb4653289499de09a1ab6e05d2a9ff587efae6ac71d1bf0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0xe5f5da2f5984bd0a29ea398b8a0a8ea300c17438a1677a7154bf111a5d744af6e846d3810a68181fbbd44961be6de5f90ac02fd7f1e23e4ef763a22e192defbc", + "blockHash": "0x8b28bbe88fa33b094348df4b8bcda8bca6975e676ca0480bc967471d4181b20a", + "blockNumber": "0x57d", + "blockTimestamp": "0x6a5dfbd1", + "transactionHash": "0x4bd7a4a74cca115759e5ba815acc24b2d44abb8faafc6050204e27d15ef42873", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8b28bbe88fa33b094348df4b8bcda8bca6975e676ca0480bc967471d4181b20a", + "blockNumber": "0x57d", + "blockTimestamp": "0x6a5dfbd1", + "transactionHash": "0x4bd7a4a74cca115759e5ba815acc24b2d44abb8faafc6050204e27d15ef42873", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x503576d6278c99a4ec800e90f4d117e6c0b36565097e2ebe7ba9053e60da1a3153b260feab1d79ba557d184c909d72dc758328cabc789c01f04613f5e3c9e993", + "blockHash": "0xa5dba164669cc77451da4d09ae4dcfa4634184157792597eb35c8f4d970566b2", + "blockNumber": "0x57e", + "blockTimestamp": "0x6a5dfbd2", + "transactionHash": "0x6093e719b9828f04396a37cae848e491c8298d6d73e3792cb12cee8e88de5e1d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa5dba164669cc77451da4d09ae4dcfa4634184157792597eb35c8f4d970566b2", + "blockNumber": "0x57e", + "blockTimestamp": "0x6a5dfbd2", + "transactionHash": "0x6093e719b9828f04396a37cae848e491c8298d6d73e3792cb12cee8e88de5e1d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a" + ], + "data": "0x432f8c7f70e8a29fef284123b222053360bf1a0daf5a2d13ad8ac3ba0cdf8f72850e0a39292cbab09f74da0577674550bf9932a318a365f962fea1f1af9e5de6", + "blockHash": "0x7342cab6547753359c2f719949bf118dc1770d9173f3801f865aced76b372e79", + "blockNumber": "0x580", + "blockTimestamp": "0x6a5dfbd3", + "transactionHash": "0xd6d4e8dfbfeb63390bd544fa5690a55ef9bb7e7fb9f7bc08af19836f23c0b9ba", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7342cab6547753359c2f719949bf118dc1770d9173f3801f865aced76b372e79", + "blockNumber": "0x580", + "blockTimestamp": "0x6a5dfbd3", + "transactionHash": "0xd6d4e8dfbfeb63390bd544fa5690a55ef9bb7e7fb9f7bc08af19836f23c0b9ba", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0d0a33e811b04f395dc06958532acb5a68f713f7802f520cf95322e0cf501dc5", + "blockNumber": "0x581", + "blockTimestamp": "0x6a5dfbd3", + "transactionHash": "0x7015be7f8ee3adac7e848dc7aef55fe22491a92d430caae31628be3b53e613df", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x24092e38cc127040efd40b6d86b82b97b90805bfe0abf31230532de6d456f10a", + "0xcf182f3f67e78c88b43d7d00a0e949e42b62ddbbf948fa06f47740cd6e8f657e", + "0x1bc69dbb4cfe2492ef355e17498a4a1bb44bd64fa24cddde004aadb7727f0aef" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0xe039b226dae052dbd66d0d1a7c67cde7c9d8fb1a290723858fe7aa584ffb3c28", + "blockNumber": "0x583", + "blockTimestamp": "0x6a5dfbd4", + "transactionHash": "0x3d1c1245af68c21014dca15fed2ad57e0ca02cea3c4c579abba55972df264d3c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xa4f2ef80bea74736afd56ab3a15b4b7c80fabfe0", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003854e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe039b226dae052dbd66d0d1a7c67cde7c9d8fb1a290723858fe7aa584ffb3c28", + "blockNumber": "0x583", + "blockTimestamp": "0x6a5dfbd4", + "transactionHash": "0x3d1c1245af68c21014dca15fed2ad57e0ca02cea3c4c579abba55972df264d3c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x000000000000000000000000000000000000000000000000000000000000000a" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000584000000000000000000000000000000000000000000000000000000006a5dfbd5b575045439b78712838366986f985e35b681f471137759053c49e0d2a83b8d69000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbca34899d8d5e51b1061e3e6791465acd92ce9c068e85c6c71de58080fd1ac03", + "blockNumber": "0x584", + "blockTimestamp": "0x6a5dfbd5", + "transactionHash": "0x58b33b20025703d73298cacc09f398881221da44de8e12ccd7d3274f0a0fac80", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x000000000000000000000000000000000000000000000000000000000000000b" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000585000000000000000000000000000000000000000000000000000000006a5dfbd5acb97ae416acc202e85c7a6e424127849276038f2c101ab05232b3942d29651e000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc0f0e8dbf99f35c3ccc6b1f46e1015a0a261cfb04a07313aebef3a6b60609c80", + "blockNumber": "0x585", + "blockTimestamp": "0x6a5dfbd5", + "transactionHash": "0x4b21f2465d19357f23c4245e2921c103f3a620b63133da016a749119f3debd3c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x346b3df038fe9f8380071ec6514d5a83ad143939", + "topics": [ + "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98", + "0x0000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a", + "0x000000000000000000000000000000000000000000000000000000000000000c" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144415bf3630000000000000000000000000000000000000000000000000000000000007a690000000000000000000000003804b81f3ddfc34654211335e08e8837aa2d3f1a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000586000000000000000000000000000000000000000000000000000000006a5dfbd547afd7eb50ea99fb870e1952a96a2b2891f213f1989c97e80027e4be81950eec000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001048656c6c6f2076726f6d2044617665210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x468a87e51efd6140ba32c0ae5019084bda6c5365057b18d649884fa2946c5f43", + "blockNumber": "0x586", + "blockTimestamp": "0x6a5dfbd5", + "transactionHash": "0x32567f9acf9d499a0fa2007f0b6673093bf7ad9a560d96a47956ee1c919949f8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x20dc312f92abdf1e450e11dadf1bf070681f72c9c89cd6fb35b72ff8793b1c08", + "0x44cad2e9423059f845ca57ac48a4499a35efb7b42b35c66842a9db6eb2a55363", + "0x70843900ffa0b85f86f7b63a38c6dc07bd3a37f592597412e7f8d58bd35ae39f" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x4d6cd9a210fd5c09e543d12c5bf2b30a6869b134af98c072166ffa8c881aa6b8", + "blockNumber": "0x687", + "blockTimestamp": "0x6a5dfbdb", + "transactionHash": "0x9be58cdabf41aa8404fd69c996814a720cbbacec93ac313590bf99a36bba5992", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xf426647e099cce24defa48c4ffa0a7de22ccc73b", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4d6cd9a210fd5c09e543d12c5bf2b30a6869b134af98c072166ffa8c881aa6b8", + "blockNumber": "0x687", + "blockTimestamp": "0x6a5dfbdb", + "transactionHash": "0x9be58cdabf41aa8404fd69c996814a720cbbacec93ac313590bf99a36bba5992", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xcf3f6ef22a36fe566b6d384270a50ca1c92bd3a14f7ed80023c4daf57e126972", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x5da3e2f42204fa3d419c647a5c1f7c2a7cc87060d26eac1fc9045d1c946adcd9" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x1b39a14556818f0865a9c0897b5c9d0a23f369a9971b3b6c36792e93dd6ad60a", + "blockNumber": "0x688", + "blockTimestamp": "0x6a5dfbdc", + "transactionHash": "0x33c37715c6f8bf8fc5660178572df738a0cebbc8fbc733b4049f47601b88033d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x06a788f8570f902de1b36b46569999d706bccdd8", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001c014000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1b39a14556818f0865a9c0897b5c9d0a23f369a9971b3b6c36792e93dd6ad60a", + "blockNumber": "0x688", + "blockTimestamp": "0x6a5dfbdc", + "transactionHash": "0x33c37715c6f8bf8fc5660178572df738a0cebbc8fbc733b4049f47601b88033d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0xc575c3ccb81fc71bc5fe519f26581d6adce31d8161013836aaa58a5eac0250f9", + "blockNumber": "0x689", + "blockTimestamp": "0x6a5dfbdd", + "transactionHash": "0xc79d553d6093c4fe31d796806120d6057e1a7bb702fe7bd4e814105ec34576d7", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000fb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "blockHash": "0x2ee235da81be61ebe9c823e46b821a6d455d822f420e5250f693b2ba3e7fe5cb", + "blockNumber": "0x68a", + "blockTimestamp": "0x6a5dfbdd", + "transactionHash": "0x1c2f5031c0ad1d1c47418613e9887d3a24d4ffea310779755d5cabbe041f850b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000d01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6000000000000000000000000fb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "blockHash": "0x2ee235da81be61ebe9c823e46b821a6d455d822f420e5250f693b2ba3e7fe5cb", + "blockNumber": "0x68a", + "blockTimestamp": "0x6a5dfbdd", + "transactionHash": "0x1c2f5031c0ad1d1c47418613e9887d3a24d4ffea310779755d5cabbe041f850b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x0a242da6706fab1ed52cfaf047d4939b8c7acac1fe8ff75d911758adf345bdda", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x5f625d64c9e18a15af2199c66a7d9a9817d710724cb79f3c1181aee33dfb35d2", + "blockNumber": "0x68d", + "blockTimestamp": "0x6a5dfbe7", + "transactionHash": "0x10efcdf352ee5c6de5fa7a003162fa62767ab533aab387007bc8de692326f5a1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a001c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xd8b3f30ee9c40771f374ce438584ecdfece191a93bfb2b0f4a37ab4845135224", + "blockNumber": "0x68e", + "blockTimestamp": "0x6a5dfbe7", + "transactionHash": "0xb206f81de5aad6308597c3c34074af83c0f49a2561d836e3b7485622cfa7e267", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65" + ], + "data": "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf3601c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x1002180006b5c71d2ff4907ffdef58108538f711766579759a9c1adb6c9ca1af", + "blockNumber": "0x693", + "blockTimestamp": "0x6a5dfbea", + "transactionHash": "0x8c045ea82c81169306bc19ff4ddd7e33c103b9582ae77e8751a889419a4f6842", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7b", + "blockHash": "0x1002180006b5c71d2ff4907ffdef58108538f711766579759a9c1adb6c9ca1af", + "blockNumber": "0x693", + "blockTimestamp": "0x6a5dfbea", + "transactionHash": "0x8c045ea82c81169306bc19ff4ddd7e33c103b9582ae77e8751a889419a4f6842", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb84d9c61cd8723fdefde7c11a847eb18057eb7ddebad9754df5585a06dfe3c7bfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442", + "blockHash": "0x2bbe7018271297c227a7a6c4de422614c7af5d076622cc476c9c03b0ca5bef26", + "blockNumber": "0x695", + "blockTimestamp": "0x6a5dfbeb", + "transactionHash": "0x24000bae22b5c287355d7f8ebc556ea1dfe50735e4bffaf56d249e00998275f3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2bbe7018271297c227a7a6c4de422614c7af5d076622cc476c9c03b0ca5bef26", + "blockNumber": "0x695", + "blockTimestamp": "0x6a5dfbeb", + "transactionHash": "0x24000bae22b5c287355d7f8ebc556ea1dfe50735e4bffaf56d249e00998275f3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xfae3c5abac4046abceaf5a33cb16e84ac849f7c325ec741c896fc4c0f8c44442c28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe317", + "blockHash": "0xdbdd3a05a5595a3b0a619a33187a0b550e59eb70742cab01bdc3af37a981af05", + "blockNumber": "0x696", + "blockTimestamp": "0x6a5dfbeb", + "transactionHash": "0xe10627cab464a60c00f89b58ff0e203a6751baab65104645acb7a1dc66d440fa", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdbdd3a05a5595a3b0a619a33187a0b550e59eb70742cab01bdc3af37a981af05", + "blockNumber": "0x696", + "blockTimestamp": "0x6a5dfbeb", + "transactionHash": "0xe10627cab464a60c00f89b58ff0e203a6751baab65104645acb7a1dc66d440fa", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc28f2aae7185b70cc5848392256e81e8ee2be3db1dd3180bce05061f4aabe3176c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd602", + "blockHash": "0xe6a02d8dbf9598fade7080bec70f96c2dc0b31dc8e5349d268bd1f63bd406719", + "blockNumber": "0x698", + "blockTimestamp": "0x6a5dfbec", + "transactionHash": "0x24c2b968a9a881bb085f5aab78c07c2a4153ec9c4ad25a635a0d2a4788f9fecf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe6a02d8dbf9598fade7080bec70f96c2dc0b31dc8e5349d268bd1f63bd406719", + "blockNumber": "0x698", + "blockTimestamp": "0x6a5dfbec", + "transactionHash": "0x24c2b968a9a881bb085f5aab78c07c2a4153ec9c4ad25a635a0d2a4788f9fecf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6c8d3896e3e61f7e1ff9e169873e65d80b960aabb5f1b6ef4b7de2a4174dd6023080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a1248", + "blockHash": "0x3aea7feb225df4f010785cbfd696edfe112cbcd43d6e3e68a9bf691e3c299e01", + "blockNumber": "0x699", + "blockTimestamp": "0x6a5dfbed", + "transactionHash": "0x70d850cd5d8b0fb69ebc5a731c530ddf9d649876942e4d8a88994dd1ea53f1bf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3aea7feb225df4f010785cbfd696edfe112cbcd43d6e3e68a9bf691e3c299e01", + "blockNumber": "0x699", + "blockTimestamp": "0x6a5dfbed", + "transactionHash": "0x70d850cd5d8b0fb69ebc5a731c530ddf9d649876942e4d8a88994dd1ea53f1bf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3080d81d17f83c2716a3256073f24118d824af01bf50b1e2f9166208572a12487df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0ca", + "blockHash": "0xb757c8244caa52eac221e5c868829a57dd0b1abf4a5b21ba7c755e7b8a2797d3", + "blockNumber": "0x69b", + "blockTimestamp": "0x6a5dfbed", + "transactionHash": "0xd998426762d42a290898d3a8e06f558741a462697ea7a3257bb0685211e1a5d3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb757c8244caa52eac221e5c868829a57dd0b1abf4a5b21ba7c755e7b8a2797d3", + "blockNumber": "0x69b", + "blockTimestamp": "0x6a5dfbed", + "transactionHash": "0xd998426762d42a290898d3a8e06f558741a462697ea7a3257bb0685211e1a5d3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x7df97e29ce32929913337f6e938ddea9a1b6d1e92bf68e21df3cd44f6a39a0caba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c0", + "blockHash": "0xce0e3199f2a0f7cf99a348451530d59bdb06dfef0be8a932f588539a57179a79", + "blockNumber": "0x69c", + "blockTimestamp": "0x6a5dfbee", + "transactionHash": "0x9efb9466f74c87ee5deae3d2251fdb2a714e1aa75f71cbc377796aca570d9cab", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xce0e3199f2a0f7cf99a348451530d59bdb06dfef0be8a932f588539a57179a79", + "blockNumber": "0x69c", + "blockTimestamp": "0x6a5dfbee", + "transactionHash": "0x9efb9466f74c87ee5deae3d2251fdb2a714e1aa75f71cbc377796aca570d9cab", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xba0997409b374466b21339a25201fda59a11e767f3d7393f40c5adb1c48b93c010f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583", + "blockHash": "0x3dd713fee1794f77e68421fc9452b55a01ae6c16d43eefcd7368ea2d2def797b", + "blockNumber": "0x69e", + "blockTimestamp": "0x6a5dfbee", + "transactionHash": "0x2ea3cfba81a84f9dcb143aae01a3ba609aee52950b94d6ae63ff9d8c26fd01e5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3dd713fee1794f77e68421fc9452b55a01ae6c16d43eefcd7368ea2d2def797b", + "blockNumber": "0x69e", + "blockTimestamp": "0x6a5dfbee", + "transactionHash": "0x2ea3cfba81a84f9dcb143aae01a3ba609aee52950b94d6ae63ff9d8c26fd01e5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x10f7a6a5a80db3968aae7a5154a3f027ff91a8d643326cd4372abd2959c23583b502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb416735601", + "blockHash": "0x87f6735fdcbf52b4bdcc5071738dc8c0f12ab413c5be3ac583d7de0a343beb16", + "blockNumber": "0x69f", + "blockTimestamp": "0x6a5dfbef", + "transactionHash": "0x48df1c9ef214e9d0c70c53e8c07464d332ee2b42481257c89d54be9bcd621622", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x87f6735fdcbf52b4bdcc5071738dc8c0f12ab413c5be3ac583d7de0a343beb16", + "blockNumber": "0x69f", + "blockTimestamp": "0x6a5dfbef", + "transactionHash": "0x48df1c9ef214e9d0c70c53e8c07464d332ee2b42481257c89d54be9bcd621622", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb502beaa6a960ec7ee9709d0a8969c687faddf2d9f744f1a71e31fb4167356019a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6", + "blockHash": "0x2a67d01b82e77f3a9b9bafc547b35e8758e5376397227a3d7d5eed7207c82e98", + "blockNumber": "0x6a1", + "blockTimestamp": "0x6a5dfbf0", + "transactionHash": "0xfaa460b785cdf72fec88e46cc9eaac899e9d408eb8851746ba7c17d8fd3ede07", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2a67d01b82e77f3a9b9bafc547b35e8758e5376397227a3d7d5eed7207c82e98", + "blockNumber": "0x6a1", + "blockTimestamp": "0x6a5dfbf0", + "transactionHash": "0xfaa460b785cdf72fec88e46cc9eaac899e9d408eb8851746ba7c17d8fd3ede07", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9a826c1a0f9f092072f4b42dfa0c470c9f128ef4fa965d1f02d8e12fc5abb9b6d95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296", + "blockHash": "0xe22fe4ca97dab1c5fae5b1b9ec509ad957f42d47ff92ab2637c58f62e4c9145c", + "blockNumber": "0x6a2", + "blockTimestamp": "0x6a5dfbf0", + "transactionHash": "0xb1c8cd75de49292b4e366c853e0a6fcc90010d8829f4075b2822072f5a974f86", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe22fe4ca97dab1c5fae5b1b9ec509ad957f42d47ff92ab2637c58f62e4c9145c", + "blockNumber": "0x6a2", + "blockTimestamp": "0x6a5dfbf0", + "transactionHash": "0xb1c8cd75de49292b4e366c853e0a6fcc90010d8829f4075b2822072f5a974f86", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd95e2adf9ff1f31f14fcfc561d08e4e08352f901bc7fe8e8dee4838c4a328296120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1dea", + "blockHash": "0xf2b0db2508186a182ac495fbd393d812bee1628fb018f372fe24b9f41187ea25", + "blockNumber": "0x6a4", + "blockTimestamp": "0x6a5dfbf1", + "transactionHash": "0x3cff3deea81f087d5e602d38c3f76b3ebf45f6b112ad3d3e72c897e2b8fbf0be", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf2b0db2508186a182ac495fbd393d812bee1628fb018f372fe24b9f41187ea25", + "blockNumber": "0x6a4", + "blockTimestamp": "0x6a5dfbf1", + "transactionHash": "0x3cff3deea81f087d5e602d38c3f76b3ebf45f6b112ad3d3e72c897e2b8fbf0be", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x120b15c68a5d29b4fbdb2993095412558d0ded783b3ad12615ff1edad04e1deaa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f171", + "blockHash": "0xe63068474ba1418f38e5c8269d5babeada960c2060c5dd03ab518e9741898795", + "blockNumber": "0x6a5", + "blockTimestamp": "0x6a5dfbf2", + "transactionHash": "0xe46bd47addc051503c3613555e3c3ec42682dc7f43703352ba217a48c961f63d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe63068474ba1418f38e5c8269d5babeada960c2060c5dd03ab518e9741898795", + "blockNumber": "0x6a5", + "blockTimestamp": "0x6a5dfbf2", + "transactionHash": "0xe46bd47addc051503c3613555e3c3ec42682dc7f43703352ba217a48c961f63d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa20f370d876506cf9c230f6ec70bc84cd457530ef0d6edfbab827e90d1d5f1714397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102", + "blockHash": "0xed544aa94823c2c346f3ca0d5a0bd7c1c8b9857b54ae437e2ae3a05a83a99ab9", + "blockNumber": "0x6a7", + "blockTimestamp": "0x6a5dfbf2", + "transactionHash": "0xd783216fc332e729a6f990c1c9fc1628a447478394d8a61ddff44b887feff7d3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xed544aa94823c2c346f3ca0d5a0bd7c1c8b9857b54ae437e2ae3a05a83a99ab9", + "blockNumber": "0x6a7", + "blockTimestamp": "0x6a5dfbf2", + "transactionHash": "0xd783216fc332e729a6f990c1c9fc1628a447478394d8a61ddff44b887feff7d3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4397f1182b96f36aaa5608230baea09423bf99bfc8209556320462366040d102ff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a87", + "blockHash": "0xa5c7baa0cb3f64f4e8bf0987c905966168508f3ba57accd4a7c005f96837e92b", + "blockNumber": "0x6a8", + "blockTimestamp": "0x6a5dfbf3", + "transactionHash": "0x42dcddebf134624cd1a8633fe3a9365f5a5d22ba3797732d510374f1fca274d1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa5c7baa0cb3f64f4e8bf0987c905966168508f3ba57accd4a7c005f96837e92b", + "blockNumber": "0x6a8", + "blockTimestamp": "0x6a5dfbf3", + "transactionHash": "0x42dcddebf134624cd1a8633fe3a9365f5a5d22ba3797732d510374f1fca274d1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xff41a44c33de379df8bcb521c321439fd0e2eea106b6500297c11164f74d2a8715797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d84", + "blockHash": "0xf1c379f4b6d0f31d057051d5e2d343ce360b4b589b3c2ccf5ba860366f16fb80", + "blockNumber": "0x6aa", + "blockTimestamp": "0x6a5dfbf3", + "transactionHash": "0xce3163e24fe347b106c7565ad04657abac3c9ef5a827e490970c356ced5ba347", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf1c379f4b6d0f31d057051d5e2d343ce360b4b589b3c2ccf5ba860366f16fb80", + "blockNumber": "0x6aa", + "blockTimestamp": "0x6a5dfbf3", + "transactionHash": "0xce3163e24fe347b106c7565ad04657abac3c9ef5a827e490970c356ced5ba347", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x15797fcba05aac4969e2479e28bae1be8fefdd08e4bfe94bdfffc0cf09116d848dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e", + "blockHash": "0x4cd4c9de58606f4b45b1894b9c8f65e36d53997cd9b4642f6bde3a9da9b3ad0e", + "blockNumber": "0x6ab", + "blockTimestamp": "0x6a5dfbf4", + "transactionHash": "0xa3878f791d837ce7788103c15c7d4e0dc1977ba62f38382a5ddd6eb6fb79145a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4cd4c9de58606f4b45b1894b9c8f65e36d53997cd9b4642f6bde3a9da9b3ad0e", + "blockNumber": "0x6ab", + "blockTimestamp": "0x6a5dfbf4", + "transactionHash": "0xa3878f791d837ce7788103c15c7d4e0dc1977ba62f38382a5ddd6eb6fb79145a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8dc74779356d1deb2b6fc136545fef1600051f6395c5b529c3a91bad9f43249e779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5a", + "blockHash": "0xcce605a117814e6b363acfd62ca1256e6160fe1a60f26840a2630760f6400870", + "blockNumber": "0x6ad", + "blockTimestamp": "0x6a5dfbf5", + "transactionHash": "0x72762e218b9e3562e1d963c719eb816ad9563d0283cfc8cdbd3fe230db0c4b69", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcce605a117814e6b363acfd62ca1256e6160fe1a60f26840a2630760f6400870", + "blockNumber": "0x6ad", + "blockTimestamp": "0x6a5dfbf5", + "transactionHash": "0x72762e218b9e3562e1d963c719eb816ad9563d0283cfc8cdbd3fe230db0c4b69", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x779f13e6d80e1d8e5a2acad7326572296b25fec1f3014972a8cbfd6295acdc5ad4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2", + "blockHash": "0x99a39f6e7263f6ef68416df78eb6dbe0a388e2e154262273f7eead80d07a0576", + "blockNumber": "0x6ae", + "blockTimestamp": "0x6a5dfbf5", + "transactionHash": "0xdd2904a1a307334c43a08745930cd1c33fb43f32fd0c92d3af67857f7816e71e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x99a39f6e7263f6ef68416df78eb6dbe0a388e2e154262273f7eead80d07a0576", + "blockNumber": "0x6ae", + "blockTimestamp": "0x6a5dfbf5", + "transactionHash": "0xdd2904a1a307334c43a08745930cd1c33fb43f32fd0c92d3af67857f7816e71e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd4dbd2924d9961cf13ca54e771c5462b27013480ed7a4a4efca4165e0766b7f2b262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203", + "blockHash": "0x054f6369e29c4b7853692a8340b79bf2797d674d5e7466b26a70e77070955af3", + "blockNumber": "0x6b0", + "blockTimestamp": "0x6a5dfbf6", + "transactionHash": "0x28960b4d19b994b5b0fc03a0670b00693d448567cd24e58e2e7366b3d9abfa58", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x054f6369e29c4b7853692a8340b79bf2797d674d5e7466b26a70e77070955af3", + "blockNumber": "0x6b0", + "blockTimestamp": "0x6a5dfbf6", + "transactionHash": "0x28960b4d19b994b5b0fc03a0670b00693d448567cd24e58e2e7366b3d9abfa58", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xb262ed1ba3cf8f04bb62f882012c66d099b34d0d2f8e97575f5badef5b71e203dbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd87058277565", + "blockHash": "0x8db21fd7045dfcae3a777bdeca91008a72286aa108aab938b91c7399aa6047e2", + "blockNumber": "0x6b1", + "blockTimestamp": "0x6a5dfbf6", + "transactionHash": "0x0733462a0c33f7c20f75badec4b6710c63ed6a8da6f8ac07a00f1692aa936b8e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8db21fd7045dfcae3a777bdeca91008a72286aa108aab938b91c7399aa6047e2", + "blockNumber": "0x6b1", + "blockTimestamp": "0x6a5dfbf6", + "transactionHash": "0x0733462a0c33f7c20f75badec4b6710c63ed6a8da6f8ac07a00f1692aa936b8e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdbb32ddac4398e571b1ad98e560f47b63f2674bbe6247dd45b4fd870582775656564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a", + "blockHash": "0x7e69470559da7a08f890e65e66b431f5a4a91fd0fd84d1e921210d8f8470e3b8", + "blockNumber": "0x6b3", + "blockTimestamp": "0x6a5dfbf7", + "transactionHash": "0xdc04e7417871a0e76005516920e5a35b94721ca7a85572449f27b7b873acc0f2", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7e69470559da7a08f890e65e66b431f5a4a91fd0fd84d1e921210d8f8470e3b8", + "blockNumber": "0x6b3", + "blockTimestamp": "0x6a5dfbf7", + "transactionHash": "0xdc04e7417871a0e76005516920e5a35b94721ca7a85572449f27b7b873acc0f2", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6564553ff508a8d8b986bf4b0d30c8b9b11042b60aaa91aa1554694baa0c9d2a4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cab", + "blockHash": "0xe61d5cbb0cc2b6f9bb09569f3400b584be8f7c3a52ebce25062d81c66de117ae", + "blockNumber": "0x6b4", + "blockTimestamp": "0x6a5dfbf8", + "transactionHash": "0xbdf90403507d28cc46c927221ba91a2930ea7c18d495fc0269fdae7b663f4294", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe61d5cbb0cc2b6f9bb09569f3400b584be8f7c3a52ebce25062d81c66de117ae", + "blockNumber": "0x6b4", + "blockTimestamp": "0x6a5dfbf8", + "transactionHash": "0xbdf90403507d28cc46c927221ba91a2930ea7c18d495fc0269fdae7b663f4294", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4403ab6fbb43ffafaa2c00cc8f754ed6d63fca7a21ca3dc5986e043805754cabe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730", + "blockHash": "0xe0c3a6d8df048b37d53a242435f82dc49b2fc7e4d617e0669eb432eefdc4800f", + "blockNumber": "0x6b6", + "blockTimestamp": "0x6a5dfbf8", + "transactionHash": "0x1df01b537f0aa8a4a48b8fefe219e63fe7f9a57f99d805303e396966702ab988", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe0c3a6d8df048b37d53a242435f82dc49b2fc7e4d617e0669eb432eefdc4800f", + "blockNumber": "0x6b6", + "blockTimestamp": "0x6a5dfbf8", + "transactionHash": "0x1df01b537f0aa8a4a48b8fefe219e63fe7f9a57f99d805303e396966702ab988", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xe84ea86e8fdd8e706db76088cf28beefbf4ce273f4e4acefafc57112806ed730109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3", + "blockHash": "0x85a5ef1c4a800711b81d133199e54819efe6149756f004095a0f5d0399875132", + "blockNumber": "0x6b7", + "blockTimestamp": "0x6a5dfbf9", + "transactionHash": "0x80b0748ffd2e0cfeddd09126a304bb0a76e5eb675390e97892c9df30d4acb631", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x85a5ef1c4a800711b81d133199e54819efe6149756f004095a0f5d0399875132", + "blockNumber": "0x6b7", + "blockTimestamp": "0x6a5dfbf9", + "transactionHash": "0x80b0748ffd2e0cfeddd09126a304bb0a76e5eb675390e97892c9df30d4acb631", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x109bd86b9ba8c73b5832735d3d423d7e15e7b4fe7225964a24dfc5e4215d0ab3cb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882", + "blockHash": "0x330142fbce7593cd5a9f50c5e74ab2c01182b02bc7dd8d2724b4249cbc18e29f", + "blockNumber": "0x6b9", + "blockTimestamp": "0x6a5dfbf9", + "transactionHash": "0x825348021e3c7b2f9180527f80748276f10e9dbfc7d45e405a5371014e2be6cf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x330142fbce7593cd5a9f50c5e74ab2c01182b02bc7dd8d2724b4249cbc18e29f", + "blockNumber": "0x6b9", + "blockTimestamp": "0x6a5dfbf9", + "transactionHash": "0x825348021e3c7b2f9180527f80748276f10e9dbfc7d45e405a5371014e2be6cf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcb81f722ccf6eeb2272f9a59defdb37dbe07950941ca039a8ad251018da75882c962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c", + "blockHash": "0xd9903cdb1634c72c1536cbe481d6a8c524a0ae19c3c4d98a68af7364d6d0605f", + "blockNumber": "0x6ba", + "blockTimestamp": "0x6a5dfbfa", + "transactionHash": "0xf2cbd0e9f913ea93529c957709da70dfb54da13c7deb38920d891c2c9090547b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd9903cdb1634c72c1536cbe481d6a8c524a0ae19c3c4d98a68af7364d6d0605f", + "blockNumber": "0x6ba", + "blockTimestamp": "0x6a5dfbfa", + "transactionHash": "0xf2cbd0e9f913ea93529c957709da70dfb54da13c7deb38920d891c2c9090547b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc962d8c2eb28beba93e86360138ff98e0660ed1020dcdd988803c416f5fb429c69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693", + "blockHash": "0x8225179210d5a7fa599f60b8bab8f15aff73905d43842af71597cb48a8f4f38c", + "blockNumber": "0x6bc", + "blockTimestamp": "0x6a5dfbfb", + "transactionHash": "0x5606532e56e6d52b3e6004b8400a8f3bdfdf3d20e74e395738842ed52f5d7d23", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8225179210d5a7fa599f60b8bab8f15aff73905d43842af71597cb48a8f4f38c", + "blockNumber": "0x6bc", + "blockTimestamp": "0x6a5dfbfb", + "transactionHash": "0x5606532e56e6d52b3e6004b8400a8f3bdfdf3d20e74e395738842ed52f5d7d23", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x69a79542efc17cf951d452cce316d2f19215161552500081b6ca0df7d9cde693a0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac3", + "blockHash": "0x5f17be0fff8b113cb4d9716c750a5946e0f32470ddf195a81b878aa9f848ac01", + "blockNumber": "0x6bd", + "blockTimestamp": "0x6a5dfbfb", + "transactionHash": "0x0de638b0af1a4272d750d51bfe0844fe57f9efe7a56f93a74b27d6cfeebceb64", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5f17be0fff8b113cb4d9716c750a5946e0f32470ddf195a81b878aa9f848ac01", + "blockNumber": "0x6bd", + "blockTimestamp": "0x6a5dfbfb", + "transactionHash": "0x0de638b0af1a4272d750d51bfe0844fe57f9efe7a56f93a74b27d6cfeebceb64", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xa0d4b719d4d8a30cb2e4d239ca7982fa65b9dbf5abfddf9452ed78cfbaf83ac31138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37f", + "blockHash": "0x4e69991d309b3e9f6efb38b8b980e751ba7c2f487ac7de4a20ae9adaa85e6584", + "blockNumber": "0x6bf", + "blockTimestamp": "0x6a5dfbfc", + "transactionHash": "0xef2dc2498e4bbd1814ecc3672977ba606a990c7b2ee3ba0aa0821e60de055587", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4e69991d309b3e9f6efb38b8b980e751ba7c2f487ac7de4a20ae9adaa85e6584", + "blockNumber": "0x6bf", + "blockTimestamp": "0x6a5dfbfc", + "transactionHash": "0xef2dc2498e4bbd1814ecc3672977ba606a990c7b2ee3ba0aa0821e60de055587", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1138315312632aa836293af9598cafaabb80d96f736ad3af4a9381c8d568b37fdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653ab", + "blockHash": "0xc0ff30073f7be7383cde583f3eb5bf41167125bb5de2c89ba11682fc28650b57", + "blockNumber": "0x6c0", + "blockTimestamp": "0x6a5dfbfd", + "transactionHash": "0xb9824551f35a928a32a82ae5a03695ee2d469b4564074468af6fa15c5d142f29", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc0ff30073f7be7383cde583f3eb5bf41167125bb5de2c89ba11682fc28650b57", + "blockNumber": "0x6c0", + "blockTimestamp": "0x6a5dfbfd", + "transactionHash": "0xb9824551f35a928a32a82ae5a03695ee2d469b4564074468af6fa15c5d142f29", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xdd4c1c4a55da0cd89afdfb4689b129b67482420fa50af78b2b239425e87653abc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba633", + "blockHash": "0xc1c8560c16c56cd5629b1b5199bca100f84096a6a58347abb2d62d087e1998a4", + "blockNumber": "0x6c2", + "blockTimestamp": "0x6a5dfbfd", + "transactionHash": "0xc4d918c7623a6f5683fcc82daf0174e9edb6f5214afd4a2ac4e65eb62da7d2cf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc1c8560c16c56cd5629b1b5199bca100f84096a6a58347abb2d62d087e1998a4", + "blockNumber": "0x6c2", + "blockTimestamp": "0x6a5dfbfd", + "transactionHash": "0xc4d918c7623a6f5683fcc82daf0174e9edb6f5214afd4a2ac4e65eb62da7d2cf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xc1c26f7c8e428ff4ac8657cbafdb3080aa11768ee843d5a7222e15f49bcba6338ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d1790", + "blockHash": "0x5e43397542c3b9012c0d5271a9c3edf7a1408ac355375e5f5e0f631892a95b12", + "blockNumber": "0x6c3", + "blockTimestamp": "0x6a5dfbfe", + "transactionHash": "0xc2af715d6cadaf030830865030920ef4fee5b1f27d7acef9bf832eb01f8fb861", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5e43397542c3b9012c0d5271a9c3edf7a1408ac355375e5f5e0f631892a95b12", + "blockNumber": "0x6c3", + "blockTimestamp": "0x6a5dfbfe", + "transactionHash": "0xc2af715d6cadaf030830865030920ef4fee5b1f27d7acef9bf832eb01f8fb861", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x8ed42d5d152fd5f3212c6646a07778dcf5c2c91aae19a8c2fd5b6c5a656d179002686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca", + "blockHash": "0x0d2b2b73f113882f562d2b15d362c24eff409f9f33bce9308ae10c2164b21e5c", + "blockNumber": "0x6c5", + "blockTimestamp": "0x6a5dfbfe", + "transactionHash": "0xb6c685bfe5794954c629fcdbf5ce782fd5e23e4486f0f7a2465a7c0b47de24ba", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0d2b2b73f113882f562d2b15d362c24eff409f9f33bce9308ae10c2164b21e5c", + "blockNumber": "0x6c5", + "blockTimestamp": "0x6a5dfbfe", + "transactionHash": "0xb6c685bfe5794954c629fcdbf5ce782fd5e23e4486f0f7a2465a7c0b47de24ba", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x02686a39874a16db37b1ed0ce6035751306d253a152ccb1275ccc22db8cebeca9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f36331", + "blockHash": "0xfa5143fdc8ab5031a8fcdbc182fbe036fff96c420c90e5bcd5fc8d71e7c1d6bc", + "blockNumber": "0x6c6", + "blockTimestamp": "0x6a5dfbff", + "transactionHash": "0x12411b9a66ffabd11e10839d1353c737032a2eb893170bd07df69c92f63f9c93", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfa5143fdc8ab5031a8fcdbc182fbe036fff96c420c90e5bcd5fc8d71e7c1d6bc", + "blockNumber": "0x6c6", + "blockTimestamp": "0x6a5dfbff", + "transactionHash": "0x12411b9a66ffabd11e10839d1353c737032a2eb893170bd07df69c92f63f9c93", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x9fe9040c1a411019c0de6ab95d214a40d1af4c4816f6d069de89a367e2f3633137a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f1", + "blockHash": "0x99422992008dc286e5bb330711cd9f156c7924249afcf1b6977261e108918f3a", + "blockNumber": "0x6c8", + "blockTimestamp": "0x6a5dfc00", + "transactionHash": "0x2fb420c020f7d7e2718fc6b03da021db4bc43a9218bb2e0513e297d9c1c63ded", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x99422992008dc286e5bb330711cd9f156c7924249afcf1b6977261e108918f3a", + "blockNumber": "0x6c8", + "blockTimestamp": "0x6a5dfc00", + "transactionHash": "0x2fb420c020f7d7e2718fc6b03da021db4bc43a9218bb2e0513e297d9c1c63ded", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x37a11b8335a1cbb9f0efd70d745a7c8bb40039296e1da7815fde0ece16f698f11fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7", + "blockHash": "0x4fdeadef736821423495cd6fe3a6f6c334a691f923303966c91eef4b5cdbcae4", + "blockNumber": "0x6c9", + "blockTimestamp": "0x6a5dfc00", + "transactionHash": "0xb4d3a90228da38dfe04e396996bda48e285d4e0471b1c176ea20cc71d53ffa51", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4fdeadef736821423495cd6fe3a6f6c334a691f923303966c91eef4b5cdbcae4", + "blockNumber": "0x6c9", + "blockTimestamp": "0x6a5dfc00", + "transactionHash": "0xb4d3a90228da38dfe04e396996bda48e285d4e0471b1c176ea20cc71d53ffa51", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x1fe18690e5a4e02668a9d46a1eaaf6db395433c458a1784a9cab2ae0809df9a7cbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f689", + "blockHash": "0x904cb56e0de62c2e2e6f3467da6a7377551677c2769e0bfd5a71dbd30aa055c9", + "blockNumber": "0x6cb", + "blockTimestamp": "0x6a5dfc01", + "transactionHash": "0xddc89c317eb281e0d3df93690c84e853eead44fd1ccdebd603843438b38a218a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x904cb56e0de62c2e2e6f3467da6a7377551677c2769e0bfd5a71dbd30aa055c9", + "blockNumber": "0x6cb", + "blockTimestamp": "0x6a5dfc01", + "transactionHash": "0xddc89c317eb281e0d3df93690c84e853eead44fd1ccdebd603843438b38a218a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbc5a65160c784b38da33296f11bd2d2e2707e952aded37d356605937704f6896125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f183", + "blockHash": "0xfd52e9394d15b6fbfbcd48295184ba44f548b8b02f59952247e84af6c90a3982", + "blockNumber": "0x6cc", + "blockTimestamp": "0x6a5dfc01", + "transactionHash": "0xeed5c620548efe1d190fcd6278a2cbae5fff080d8d849a8b6c2a0d5ff70f9e57", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfd52e9394d15b6fbfbcd48295184ba44f548b8b02f59952247e84af6c90a3982", + "blockNumber": "0x6cc", + "blockTimestamp": "0x6a5dfc01", + "transactionHash": "0xeed5c620548efe1d190fcd6278a2cbae5fff080d8d849a8b6c2a0d5ff70f9e57", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x6125621ea7d9b1d140f89575ca1526cec19e21acaec169e6ab4bcd7f7b38f18303e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d", + "blockHash": "0x51f6057c03edf958089c7a26a591ec97a851e95e6c9ef533515bcfc3cbbc7d98", + "blockNumber": "0x6ce", + "blockTimestamp": "0x6a5dfc02", + "transactionHash": "0x7d91e6bc732cbadc122d37b5ff10105dbd5babf8a7d3bc97f2c08be85bda24be", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x51f6057c03edf958089c7a26a591ec97a851e95e6c9ef533515bcfc3cbbc7d98", + "blockNumber": "0x6ce", + "blockTimestamp": "0x6a5dfc02", + "transactionHash": "0x7d91e6bc732cbadc122d37b5ff10105dbd5babf8a7d3bc97f2c08be85bda24be", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x03e005bfeb0efd6e5e1f87a4983bc7ca2d45c10d3018571d0a0514487321fb2d3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a", + "blockHash": "0x68094ef392b86ffb72330f0bba88e02849eace8644ce69cc62a309e9aecf4971", + "blockNumber": "0x6cf", + "blockTimestamp": "0x6a5dfc03", + "transactionHash": "0xcde4536f7bb7860bac77aa642441971527c5a784c387aaa220b95818d88afba3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x68094ef392b86ffb72330f0bba88e02849eace8644ce69cc62a309e9aecf4971", + "blockNumber": "0x6cf", + "blockTimestamp": "0x6a5dfc03", + "transactionHash": "0xcde4536f7bb7860bac77aa642441971527c5a784c387aaa220b95818d88afba3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x3cb7769c6a4cfef5748921b8c02e5513b6fbd496b754906a61d783be8876e22a2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f88", + "blockHash": "0x9e4e4b746b3f13858660f535cec502fc5ceab8781218ea1d4076f20202e54d55", + "blockNumber": "0x6d1", + "blockTimestamp": "0x6a5dfc03", + "transactionHash": "0x1b3b29ea876f5f7792f1acc7ecf3bbd5d1b1577bc1c928a86977c00fecd1e7c8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9e4e4b746b3f13858660f535cec502fc5ceab8781218ea1d4076f20202e54d55", + "blockNumber": "0x6d1", + "blockTimestamp": "0x6a5dfc03", + "transactionHash": "0x1b3b29ea876f5f7792f1acc7ecf3bbd5d1b1577bc1c928a86977c00fecd1e7c8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x2f96fa439ce609b4526e5d1ad0761ca064cb25c5758d1c4df4a7ba0983e80f885a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd8925", + "blockHash": "0x9879959614224df01e67a237051e7339a5c3b5765734c22d3be624af2efe721e", + "blockNumber": "0x6d2", + "blockTimestamp": "0x6a5dfc04", + "transactionHash": "0xb8f30f80267fa0307af0f7403075d1aaaf7588d387183d29354b7d28e32f87ff", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9879959614224df01e67a237051e7339a5c3b5765734c22d3be624af2efe721e", + "blockNumber": "0x6d2", + "blockTimestamp": "0x6a5dfc04", + "transactionHash": "0xb8f30f80267fa0307af0f7403075d1aaaf7588d387183d29354b7d28e32f87ff", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x5a4e6b813b8b0636c63ae87ab1e2fb689ef7a90593d61512dbddc19236cd89254b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd77", + "blockHash": "0xc264fa343f9c0483bf0c4c7c6448c19baa2d898fde6a558c51193335e05dec7c", + "blockNumber": "0x6d4", + "blockTimestamp": "0x6a5dfc04", + "transactionHash": "0x276e709011de881522f80a78a979ab8f08b20090a6aa91bddb5c805c6adba6e1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc264fa343f9c0483bf0c4c7c6448c19baa2d898fde6a558c51193335e05dec7c", + "blockNumber": "0x6d4", + "blockTimestamp": "0x6a5dfc04", + "transactionHash": "0x276e709011de881522f80a78a979ab8f08b20090a6aa91bddb5c805c6adba6e1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x4b4fe8e1a6b7935432d7ef5fd150e55987e2e2984812753648e8f039b406cd7771c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98b", + "blockHash": "0xf67b81b365f4588bf1dfdfae2b8ffbc62e830bcca1f8a4ed4d164135f051a983", + "blockNumber": "0x6d5", + "blockTimestamp": "0x6a5dfc05", + "transactionHash": "0x3fd06c75face3c91a67095df72437f194cb5565f6dcbbbc2809f61d141a8199a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf67b81b365f4588bf1dfdfae2b8ffbc62e830bcca1f8a4ed4d164135f051a983", + "blockNumber": "0x6d5", + "blockTimestamp": "0x6a5dfc05", + "transactionHash": "0x3fd06c75face3c91a67095df72437f194cb5565f6dcbbbc2809f61d141a8199a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0x71c0c2580458060a2a5ee71e61e9ac841e870aa391f7f5bf1925505921c7c98bcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402", + "blockHash": "0xfc37b674e698c6b54fc9b0a8c066d8cd637384dae961114684a65f9dd9b0a500", + "blockNumber": "0x6d7", + "blockTimestamp": "0x6a5dfc06", + "transactionHash": "0xc9e3b3304411a47c1393be1aa9b22e1dc5d162a68d8733f620b23259730799fd", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfc37b674e698c6b54fc9b0a8c066d8cd637384dae961114684a65f9dd9b0a500", + "blockNumber": "0x6d7", + "blockTimestamp": "0x6a5dfc06", + "transactionHash": "0xc9e3b3304411a47c1393be1aa9b22e1dc5d162a68d8733f620b23259730799fd", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xcbad0285113618734acde272929ab2dde949fb63a84b7bc16c18933c62a03402d2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b", + "blockHash": "0x66cf1908009da6f645acc3bb46c55550eb838b5dc4e3aa81442ea176febded8f", + "blockNumber": "0x6d8", + "blockTimestamp": "0x6a5dfc06", + "transactionHash": "0x910dd9fb5d9d405764da417a26bb32783e6b6547719b7106a131aa9252442279", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x66cf1908009da6f645acc3bb46c55550eb838b5dc4e3aa81442ea176febded8f", + "blockNumber": "0x6d8", + "blockTimestamp": "0x6a5dfc06", + "transactionHash": "0x910dd9fb5d9d405764da417a26bb32783e6b6547719b7106a131aa9252442279", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0" + ], + "data": "0xd2a196a7d7c48df10dbd5431708d129c7c0e936312b7d8e41f14a87df9cf026b01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0xb0ea86ca45afb9a3991e0712268bb04ce4ca3f8fb0106fad089e7ea64a919a54", + "blockNumber": "0x6da", + "blockTimestamp": "0x6a5dfc07", + "transactionHash": "0x5ca9c0d6e658c685941fdf84389b6a8d5d3f42e4eb6a06b6c06900879da12310", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb0ea86ca45afb9a3991e0712268bb04ce4ca3f8fb0106fad089e7ea64a919a54", + "blockNumber": "0x6da", + "blockTimestamp": "0x6a5dfc07", + "transactionHash": "0x5ca9c0d6e658c685941fdf84389b6a8d5d3f42e4eb6a06b6c06900879da12310", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x0000000000000000000000001259d72da50ce2a1ee137670989ef02ffd6862da" + ], + "data": "0x", + "blockHash": "0xb48e4852e4fceee17d2e4cd01f3a3220a938431ba69ee20cc37059a8c05bca28", + "blockNumber": "0x6db", + "blockTimestamp": "0x6a5dfc08", + "transactionHash": "0xd50fbe0e596dadc1a0dbc277865c411c9539b591eb29dfed7e1ae816c445100f", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000024e86000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb48e4852e4fceee17d2e4cd01f3a3220a938431ba69ee20cc37059a8c05bca28", + "blockNumber": "0x6db", + "blockTimestamp": "0x6a5dfc08", + "transactionHash": "0xd50fbe0e596dadc1a0dbc277865c411c9539b591eb29dfed7e1ae816c445100f", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x033a726bc0f8383ace5f18326de1013035d88146692f5ce9d0a107e02a6502e401c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa", + "blockHash": "0x111e849662ddc5c5bdef379c18ff23d56948241838d919a03e8783fa98dd8181", + "blockNumber": "0x6dd", + "blockTimestamp": "0x6a5dfc08", + "transactionHash": "0x9bd4a93e9c1c2881f9db63c05efccc43a25f62aab0dc0f1284c1bb05f541df08", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65" + ], + "data": "0x1be220a263240d2a80bfecce9776b351398491beedbd1858a1d1c301f38dd2af0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfdb06238406a2176317da2b594531b2e950afc8a488d6752aa0102776020a2c9", + "blockNumber": "0x6de", + "blockTimestamp": "0x6a5dfc09", + "transactionHash": "0xb88e54b76d07bff0af352bfd1a9c7d35d111e71bedb6d55d2b03387399fcf3e1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420", + "0x033a726bc0f8383ace5f18326de1013035d88146692f5ce9d0a107e02a6502e4", + "0x1be220a263240d2a80bfecce9776b351398491beedbd1858a1d1c301f38dd2af" + ], + "data": "0x8757a0746c2535d55ac4697f825ec4f6e8181c609bd6193ab7b9254b0cb14cd0", + "blockHash": "0xfdb06238406a2176317da2b594531b2e950afc8a488d6752aa0102776020a2c9", + "blockNumber": "0x6de", + "blockTimestamp": "0x6a5dfc09", + "transactionHash": "0xb88e54b76d07bff0af352bfd1a9c7d35d111e71bedb6d55d2b03387399fcf3e1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x8757a0746c2535d55ac4697f825ec4f6e8181c609bd6193ab7b9254b0cb14cd0193095a1e77f8cc05c9b7705f2dc991ba8e6449e87c1865fc81715b1e9133bbf", + "blockHash": "0xc82593b0a144afcafb3b2bab6d6cd1ab0504795b9b5216c68ed1e3e59cab9fd3", + "blockNumber": "0x6e0", + "blockTimestamp": "0x6a5dfc09", + "transactionHash": "0x800faab8836ff1262128a3f29588d7cfd218be79a2ccd0d887942f144b0f6915", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000b28d800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc82593b0a144afcafb3b2bab6d6cd1ab0504795b9b5216c68ed1e3e59cab9fd3", + "blockNumber": "0x6e0", + "blockTimestamp": "0x6a5dfc09", + "transactionHash": "0x800faab8836ff1262128a3f29588d7cfd218be79a2ccd0d887942f144b0f6915", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x193095a1e77f8cc05c9b7705f2dc991ba8e6449e87c1865fc81715b1e9133bbf17504f1f9ca3e8829bb8bfc9fddf79934353e5820896930602bf6728fd8f4e20", + "blockHash": "0x4d4c6857250371186542d281122fce246dc7fc30c68474d55d7f2a247fdb0754", + "blockNumber": "0x6e1", + "blockTimestamp": "0x6a5dfc0a", + "transactionHash": "0x1fdb22adc3518e449d35bbc7b4d09177c5e6a6760a8c4340db3c9713e35b0934", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4d4c6857250371186542d281122fce246dc7fc30c68474d55d7f2a247fdb0754", + "blockNumber": "0x6e1", + "blockTimestamp": "0x6a5dfc0a", + "transactionHash": "0x1fdb22adc3518e449d35bbc7b4d09177c5e6a6760a8c4340db3c9713e35b0934", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x17504f1f9ca3e8829bb8bfc9fddf79934353e5820896930602bf6728fd8f4e20ec223b3fc991c0bbec976f2788545b44f14e9de5a99a6087a522ad299665bc66", + "blockHash": "0xc44e158ffc0ddb55d60b661ee98d5066fe265aecce770c56caf888d65cf29fd5", + "blockNumber": "0x6e3", + "blockTimestamp": "0x6a5dfc0b", + "transactionHash": "0xa1a67a32962aca235053914b30fe1d07e966e28eec851a1aced28e002b1a6a04", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc44e158ffc0ddb55d60b661ee98d5066fe265aecce770c56caf888d65cf29fd5", + "blockNumber": "0x6e3", + "blockTimestamp": "0x6a5dfc0b", + "transactionHash": "0xa1a67a32962aca235053914b30fe1d07e966e28eec851a1aced28e002b1a6a04", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0xec223b3fc991c0bbec976f2788545b44f14e9de5a99a6087a522ad299665bc66541649518c7f0330a031a5cf3826b5f219b5fc93d41a674827a5afa383cd598a", + "blockHash": "0x64fff8565269c7bdeedbaf287fcaee42e379b9e2f0e02b9f168456acd28a6a9e", + "blockNumber": "0x6e4", + "blockTimestamp": "0x6a5dfc0c", + "transactionHash": "0x3ffebc3dbd86481a168e33c726c13e1faf91a71cd138efe80653150c1578e381", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x64fff8565269c7bdeedbaf287fcaee42e379b9e2f0e02b9f168456acd28a6a9e", + "blockNumber": "0x6e4", + "blockTimestamp": "0x6a5dfc0c", + "transactionHash": "0x3ffebc3dbd86481a168e33c726c13e1faf91a71cd138efe80653150c1578e381", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x541649518c7f0330a031a5cf3826b5f219b5fc93d41a674827a5afa383cd598ad6d93be65463873809fff693229ca3e5a2365f94c28aa02f55d3ca9328ff8a96", + "blockHash": "0x923e0a7cfa62dd0e32e1452225d3f5ea78cb8d056610d7d81b36e73626cc28c3", + "blockNumber": "0x6e6", + "blockTimestamp": "0x6a5dfc0c", + "transactionHash": "0xfde74c108eba6dd215bf147b5c406b784483f4e500c5f45567d7262580e0cde3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x923e0a7cfa62dd0e32e1452225d3f5ea78cb8d056610d7d81b36e73626cc28c3", + "blockNumber": "0x6e6", + "blockTimestamp": "0x6a5dfc0c", + "transactionHash": "0xfde74c108eba6dd215bf147b5c406b784483f4e500c5f45567d7262580e0cde3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0xd6d93be65463873809fff693229ca3e5a2365f94c28aa02f55d3ca9328ff8a9617ecf05c722b568a7f8fc7d90a624eda4ae85e5e4f9195bc4cae9e5ca0655332", + "blockHash": "0xd2c1783d0da32fd3d817954648c22f0c6d16144ee1709180485d08a760b45ada", + "blockNumber": "0x6e7", + "blockTimestamp": "0x6a5dfc0d", + "transactionHash": "0xa16f12649408adb8fa6901ab12c83ad79b11bc4f8988e1ab8d90b29a2ab8f02e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd2c1783d0da32fd3d817954648c22f0c6d16144ee1709180485d08a760b45ada", + "blockNumber": "0x6e7", + "blockTimestamp": "0x6a5dfc0d", + "transactionHash": "0xa16f12649408adb8fa6901ab12c83ad79b11bc4f8988e1ab8d90b29a2ab8f02e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x17ecf05c722b568a7f8fc7d90a624eda4ae85e5e4f9195bc4cae9e5ca0655332d79a51de0dc78b9c121ba9ace4ef79b19eb413bef318c69ece76770914b50820", + "blockHash": "0x69ad4b364f3656fa715d46d9caa73ff2b4e8a08dcb49355809b527812ce3d506", + "blockNumber": "0x6e9", + "blockTimestamp": "0x6a5dfc0e", + "transactionHash": "0x4fc228769e829b9b2dc18cb2ef3b7eba10497faa5a416f1f04f27af693eb2cb1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x69ad4b364f3656fa715d46d9caa73ff2b4e8a08dcb49355809b527812ce3d506", + "blockNumber": "0x6e9", + "blockTimestamp": "0x6a5dfc0e", + "transactionHash": "0x4fc228769e829b9b2dc18cb2ef3b7eba10497faa5a416f1f04f27af693eb2cb1", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0xd79a51de0dc78b9c121ba9ace4ef79b19eb413bef318c69ece76770914b50820d74e6d1186fe47aed09cd395a96c4c71fe92be143c878da50123bce32dffbc71", + "blockHash": "0x2d62040618e1dc3c13f9ecaf061b807d35e3cca20c768fc7869b0fc5f2ff9812", + "blockNumber": "0x6ea", + "blockTimestamp": "0x6a5dfc0f", + "transactionHash": "0x0b011c871b094e5f37124cffcc18026bf7f237e36c6cac08e47f0ccab9c5b6f8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2d62040618e1dc3c13f9ecaf061b807d35e3cca20c768fc7869b0fc5f2ff9812", + "blockNumber": "0x6ea", + "blockTimestamp": "0x6a5dfc0f", + "transactionHash": "0x0b011c871b094e5f37124cffcc18026bf7f237e36c6cac08e47f0ccab9c5b6f8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0xd74e6d1186fe47aed09cd395a96c4c71fe92be143c878da50123bce32dffbc716a427639699f3a1eabf27808ab5ef017887f4cb2c2f63c91a3921740ad7f7d84", + "blockHash": "0xcbf197cadd883d971f3c5562045740c7ef5888c788d312bde991069afaaa0867", + "blockNumber": "0x6ec", + "blockTimestamp": "0x6a5dfc0f", + "transactionHash": "0x43d0cd058d2d9ffcce5501d1cdfd28e95dc1a22d35641905eeb493c486614e20", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xcbf197cadd883d971f3c5562045740c7ef5888c788d312bde991069afaaa0867", + "blockNumber": "0x6ec", + "blockTimestamp": "0x6a5dfc0f", + "transactionHash": "0x43d0cd058d2d9ffcce5501d1cdfd28e95dc1a22d35641905eeb493c486614e20", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x6a427639699f3a1eabf27808ab5ef017887f4cb2c2f63c91a3921740ad7f7d84b44c7422d61ad08304276374146afe83d5d7624ac5ce5f7a4b00644c863af191", + "blockHash": "0x4cac515074559fc0f20c2e2b0c65eff070cea760e85b1cd8299af9801286d231", + "blockNumber": "0x6ed", + "blockTimestamp": "0x6a5dfc10", + "transactionHash": "0xb4d477089cf7284c09e67f8795530eccdadcdfd34890ff2a921b8865cc01abe4", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x4cac515074559fc0f20c2e2b0c65eff070cea760e85b1cd8299af9801286d231", + "blockNumber": "0x6ed", + "blockTimestamp": "0x6a5dfc10", + "transactionHash": "0xb4d477089cf7284c09e67f8795530eccdadcdfd34890ff2a921b8865cc01abe4", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0xb44c7422d61ad08304276374146afe83d5d7624ac5ce5f7a4b00644c863af191593fcdd1e2dcf15bb559929f1bc3ad3cae1e6408f2f301b83a8cfa203b4ccc25", + "blockHash": "0xc06f5f9cfefa3ab226fa8bd028bcadc5bb99a685fbe0e6ccf623a70d52169f3d", + "blockNumber": "0x6ef", + "blockTimestamp": "0x6a5dfc10", + "transactionHash": "0x6eb39c438ea58f328f35b788b0cabfccb1c9e9fef5077e44380fbb85e215e702", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc06f5f9cfefa3ab226fa8bd028bcadc5bb99a685fbe0e6ccf623a70d52169f3d", + "blockNumber": "0x6ef", + "blockTimestamp": "0x6a5dfc10", + "transactionHash": "0x6eb39c438ea58f328f35b788b0cabfccb1c9e9fef5077e44380fbb85e215e702", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x593fcdd1e2dcf15bb559929f1bc3ad3cae1e6408f2f301b83a8cfa203b4ccc256b77413a2615e7ae622ad6f429a04a40c2246b9950f50ca494b163fdbe7254ad", + "blockHash": "0xdd4117bdd3acc553d468806b33ff3bd05c77300f1055faf6f359ce12bdc43a08", + "blockNumber": "0x6f0", + "blockTimestamp": "0x6a5dfc12", + "transactionHash": "0xcbf212df8a55af4891e1085e667c17709e928bf6edbab0b3677c194dcfd58830", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdd4117bdd3acc553d468806b33ff3bd05c77300f1055faf6f359ce12bdc43a08", + "blockNumber": "0x6f0", + "blockTimestamp": "0x6a5dfc12", + "transactionHash": "0xcbf212df8a55af4891e1085e667c17709e928bf6edbab0b3677c194dcfd58830", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x6b77413a2615e7ae622ad6f429a04a40c2246b9950f50ca494b163fdbe7254ad096c3e6d55af996c73e56d93833a2c478e7d026507191dd93d8cc974831ffac3", + "blockHash": "0x3da57142cb7950664833b7819cd73f2d80895676bbd6e29cfe6e9e8b408ff8c2", + "blockNumber": "0x6f2", + "blockTimestamp": "0x6a5dfc12", + "transactionHash": "0xcf6ac40d188d56dcb2c241c677dbe356b7f3759cca223f982a134fc731255271", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3da57142cb7950664833b7819cd73f2d80895676bbd6e29cfe6e9e8b408ff8c2", + "blockNumber": "0x6f2", + "blockTimestamp": "0x6a5dfc12", + "transactionHash": "0xcf6ac40d188d56dcb2c241c677dbe356b7f3759cca223f982a134fc731255271", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x096c3e6d55af996c73e56d93833a2c478e7d026507191dd93d8cc974831ffac39be76003779552dd9c50bf5789b24c4050494c8f2ae0390b0519725b7ab51d12", + "blockHash": "0xfa3764a59e26c4e1c297b36edeb0a340b8ecc45e9b2be8f11432c637cf20ae89", + "blockNumber": "0x6f3", + "blockTimestamp": "0x6a5dfc13", + "transactionHash": "0x66331105acc923776cadc013634d2d2132dc738e7e2e411579eb4b52ef76deb3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfa3764a59e26c4e1c297b36edeb0a340b8ecc45e9b2be8f11432c637cf20ae89", + "blockNumber": "0x6f3", + "blockTimestamp": "0x6a5dfc13", + "transactionHash": "0x66331105acc923776cadc013634d2d2132dc738e7e2e411579eb4b52ef76deb3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x9be76003779552dd9c50bf5789b24c4050494c8f2ae0390b0519725b7ab51d124d1519af80297f41e4ee5d79796ac6102ec2f3e96b2e3045c87cca2f12990db5", + "blockHash": "0x1771cc1fb9e36ee199997510bb6c2dd7e029433864ac523721f6fb2a2f4fb337", + "blockNumber": "0x6f5", + "blockTimestamp": "0x6a5dfc13", + "transactionHash": "0xfc1bd70c3e5e619808958bc08f4870691c19635323aceb382d99e6f125f016e0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1771cc1fb9e36ee199997510bb6c2dd7e029433864ac523721f6fb2a2f4fb337", + "blockNumber": "0x6f5", + "blockTimestamp": "0x6a5dfc13", + "transactionHash": "0xfc1bd70c3e5e619808958bc08f4870691c19635323aceb382d99e6f125f016e0", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420" + ], + "data": "0x4d1519af80297f41e4ee5d79796ac6102ec2f3e96b2e3045c87cca2f12990db563d24ee36d63d79ec41f95e530359d3f8c78abcae85d9210f2aa16ccd03a256d", + "blockHash": "0xd9ae36c56a210921779ac71dff807aed325b8a7c4e82de6d1f75b5e990eff949", + "blockNumber": "0x6f6", + "blockTimestamp": "0x6a5dfc14", + "transactionHash": "0x5ceb7da7445319cc91d490f9087df64fd10daf581517daa32f1b9caf693325d8", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009127800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd9ae36c56a210921779ac71dff807aed325b8a7c4e82de6d1f75b5e990eff949", + "blockNumber": "0x6f6", + "blockTimestamp": "0x6a5dfc14", + "transactionHash": "0x5ceb7da7445319cc91d490f9087df64fd10daf581517daa32f1b9caf693325d8", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0xc7912008857e6a935ca95124a47677526500edc470687f5ced6e7ad3ca465138", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420", + "0x00000000000000000000000009291063ecd2f46c50647dae4c98733a7d641ba7" + ], + "data": "0x", + "blockHash": "0x7aea5ac5f683986283aa21fc64a8c298619b66292a8d95f94b3dce4b3d24a809", + "blockNumber": "0x6f8", + "blockTimestamp": "0x6a5dfc15", + "transactionHash": "0x206842af890f82efb27072a6b6e325b9be32ebdf11dbae56f5ce295753500a01", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000027ec3800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7aea5ac5f683986283aa21fc64a8c298619b66292a8d95f94b3dce4b3d24a809", + "blockNumber": "0x6f8", + "blockTimestamp": "0x6a5dfc15", + "transactionHash": "0x206842af890f82efb27072a6b6e325b9be32ebdf11dbae56f5ce295753500a01", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65" + ], + "data": "0x970b2080348c1b551aa94fb50df0ad93ee39130f61bbca9ef3113b5460a585b00000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf1777d9dbe0cdfb256aa353913b3918e919c2b4c0f3e469d025286633bf000b7", + "blockNumber": "0x6f9", + "blockTimestamp": "0x6a5dfc18", + "transactionHash": "0x173decd6d93b94e4c7a9c3571e2c07385eccee62f9d2f25ac973ab13d4901ca0", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xf8e98e9201e0caf973fa5520838a058bd8a819e0a8f5dd1fa08c3e550d4b9872", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955" + ], + "data": "0x06653978d701eed8529ab87815e2fe83792eb0758689f1ff475654ad06348aaa6e88cd1a6b36ce0ab38046e54ad7cc28e5f21e25841640c4dc6b2e741ff7bcfb", + "blockHash": "0x050ce90dfeb6c4aa6548363ce44772c55333dda95aa988cfebbf94379d3d1914", + "blockNumber": "0x6fb", + "blockTimestamp": "0x6a5dfc19", + "transactionHash": "0x580449b0928cf3603bed3cae0096ecdde64fcf5f4c8a9413f1e045b4f3d975ef", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbaea19df0c2b83760acad299eaf042b77e11e0f362ce10d0d4bb24b09fa5296d", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e", + "0x970b2080348c1b551aa94fb50df0ad93ee39130f61bbca9ef3113b5460a585b0", + "0x06653978d701eed8529ab87815e2fe83792eb0758689f1ff475654ad06348aaa" + ], + "data": "0x97438446afa218b59551b8e4dd4e5a0ef278a647ed1aacdbb6d11473f4de3473", + "blockHash": "0x050ce90dfeb6c4aa6548363ce44772c55333dda95aa988cfebbf94379d3d1914", + "blockNumber": "0x6fb", + "blockTimestamp": "0x6a5dfc19", + "transactionHash": "0x580449b0928cf3603bed3cae0096ecdde64fcf5f4c8a9413f1e045b4f3d975ef", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x17e8362ec983c0bbaf402d0d87a58e7c4d20b4b540e19482a4864dfedc5a5b594ac88684fb99d78e3883548b563b3818276138dbb01ac08e60c0637af96abc16", + "blockHash": "0xc80c0342bcca93da1b726d31e005f2f958b597fdc164695f11c4b7af286d4d8c", + "blockNumber": "0x6fc", + "blockTimestamp": "0x6a5dfc19", + "transactionHash": "0x5eeef9e4cc7a7fa84e8bd0c05bf91d06c79216a9fcb5e4d10e86f88a2ff71da3", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000da32000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc80c0342bcca93da1b726d31e005f2f958b597fdc164695f11c4b7af286d4d8c", + "blockNumber": "0x6fc", + "blockTimestamp": "0x6a5dfc19", + "transactionHash": "0x5eeef9e4cc7a7fa84e8bd0c05bf91d06c79216a9fcb5e4d10e86f88a2ff71da3", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xfce05f3f43d3fb99e36de2e04a73f8c49f61027722afc2136b7a8d9caacf8ba50b30c2ab902fba56f84fc1f73a37e9ae44a55c9bc99619bc548993acf632db99", + "blockHash": "0x3bbe3bf2ad31480c2a49689dc0da00e59dc1236c39a7ad0d08d36e686f6c7b72", + "blockNumber": "0x6fe", + "blockTimestamp": "0x6a5dfc1a", + "transactionHash": "0x933b39a5b8c1d1a5035e254f3b27384e79f23546d33f77a20c9e9309234a0679", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3bbe3bf2ad31480c2a49689dc0da00e59dc1236c39a7ad0d08d36e686f6c7b72", + "blockNumber": "0x6fe", + "blockTimestamp": "0x6a5dfc1a", + "transactionHash": "0x933b39a5b8c1d1a5035e254f3b27384e79f23546d33f77a20c9e9309234a0679", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x40c74b5a883af62a5cc1539aa3e1b23ee681f25f891874175f53cef5898d202c87af8cf5bf5ea2be23c325c2e65c26db473987b7365e5e3de1a3725546ae4cfe", + "blockHash": "0xe068906c980a64d72db56041c2c4442045ca3dc557cbc3b4c3478c9379aa3bf0", + "blockNumber": "0x6ff", + "blockTimestamp": "0x6a5dfc1b", + "transactionHash": "0xcaee801ff5a4884c8e3118045c7df15c18979d444bbffaa471caa9c671c2248d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xe068906c980a64d72db56041c2c4442045ca3dc557cbc3b4c3478c9379aa3bf0", + "blockNumber": "0x6ff", + "blockTimestamp": "0x6a5dfc1b", + "transactionHash": "0xcaee801ff5a4884c8e3118045c7df15c18979d444bbffaa471caa9c671c2248d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x8dbd50e5dba73a32b830a00b8d2a2062024f2c8c51679161e8883e31c698b566ee98a4fe7afccaf0a62795d40b7842d2ad702e7b00e51a461fa735d4a425d617", + "blockHash": "0xa3139409226de1521b56fed7596a1ee0c60beb7d9820cfaf684f3bc5b2216ae8", + "blockNumber": "0x701", + "blockTimestamp": "0x6a5dfc1c", + "transactionHash": "0x2b03a50294f3bd34907748f443fd68e273b15fbc89ac63fde20c67c7c6d22f05", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xa3139409226de1521b56fed7596a1ee0c60beb7d9820cfaf684f3bc5b2216ae8", + "blockNumber": "0x701", + "blockTimestamp": "0x6a5dfc1c", + "transactionHash": "0x2b03a50294f3bd34907748f443fd68e273b15fbc89ac63fde20c67c7c6d22f05", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x7bf09cd08dc361f6a11e952791ffedeea8fc8c9a0e6ccb5d32286db135c2c29b7902d27067f255206ff9ec9bf5d0e734c0f32e708f2e3bbd0e1e2aa4f3122e98", + "blockHash": "0x212b357340e9b747f58d9ed9e15a5dcfc6bbdcd3982bbe28c6ab7c503f1b500d", + "blockNumber": "0x702", + "blockTimestamp": "0x6a5dfc1c", + "transactionHash": "0x2a375c66b5a3f2e28b628b57611087e0ee5c96bb1b3253211e21076e1b1cfa5e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x212b357340e9b747f58d9ed9e15a5dcfc6bbdcd3982bbe28c6ab7c503f1b500d", + "blockNumber": "0x702", + "blockTimestamp": "0x6a5dfc1c", + "transactionHash": "0x2a375c66b5a3f2e28b628b57611087e0ee5c96bb1b3253211e21076e1b1cfa5e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x672ed5e03d0f7d6d9e854bc9214fb2b3290514557eee9138cfa9e9c2fc47464f4b3b2a8b6c5b724750347b3a83a37dbc34bd288c1c860a471019968f362c400c", + "blockHash": "0x29c1564be3820152f6309667f14a1dbdf9ca7c8132f91c1ef8157cf93636a80f", + "blockNumber": "0x704", + "blockTimestamp": "0x6a5dfc1d", + "transactionHash": "0xb0846a4f8aae0be50b4a27bcd2b7dd047332d5dc497b086906e9b45b2f6dbf7b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x29c1564be3820152f6309667f14a1dbdf9ca7c8132f91c1ef8157cf93636a80f", + "blockNumber": "0x704", + "blockTimestamp": "0x6a5dfc1d", + "transactionHash": "0xb0846a4f8aae0be50b4a27bcd2b7dd047332d5dc497b086906e9b45b2f6dbf7b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x4f2811d7f94229ec07decb813e53dfaeb6f73e02a6df359567643463d03fb279708fcec71fb3250fcf3dd5b6a2b85f4b210ef8789485a5c18e3ba9254e799bd2", + "blockHash": "0x7f22c39e4541d051345d8bc48d97de21d4269adb44d6318bbaef76440b54833e", + "blockNumber": "0x705", + "blockTimestamp": "0x6a5dfc1e", + "transactionHash": "0x4ca6b46cffe323b30195e4ccc2e30cfbafa99e783d988f9f0dbc974c8853e4de", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7f22c39e4541d051345d8bc48d97de21d4269adb44d6318bbaef76440b54833e", + "blockNumber": "0x705", + "blockTimestamp": "0x6a5dfc1e", + "transactionHash": "0x4ca6b46cffe323b30195e4ccc2e30cfbafa99e783d988f9f0dbc974c8853e4de", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x8f47c6d833feaf9e7a18158fd1ef8f2a528aab6662ee0f02ac302a772adb06ba569034641ccb7fb5d15b3ad11b1c91c2adf0da88b5739307469f86feaa6873f2", + "blockHash": "0x799322a43ecb795ac7136b7efc62f974cff8a02bff8788f87c15b49dbaa14653", + "blockNumber": "0x707", + "blockTimestamp": "0x6a5dfc1f", + "transactionHash": "0x2a6abf1642d08481e27fb074662fb9efe18edad3ca4d1819c6c13621148fe2ab", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x799322a43ecb795ac7136b7efc62f974cff8a02bff8788f87c15b49dbaa14653", + "blockNumber": "0x707", + "blockTimestamp": "0x6a5dfc1f", + "transactionHash": "0x2a6abf1642d08481e27fb074662fb9efe18edad3ca4d1819c6c13621148fe2ab", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x26b623becdc9b9bd722a09223a2708ecfba737a38e3df733404dd16798286e7e3b5fe0bfdc6e5f353cd70c2c0d5e42bd52be7d7417ad6fdcb3a570e20f01b8cc", + "blockHash": "0x9176c47228050e83cbbd5f33aa3b28f31dfd1951b2a456b3372f05f3a62d3ac7", + "blockNumber": "0x708", + "blockTimestamp": "0x6a5dfc20", + "transactionHash": "0x5ec6a6ee2bd6d7a8837341121298c65d829616e78433af87a7bdce148959b535", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9176c47228050e83cbbd5f33aa3b28f31dfd1951b2a456b3372f05f3a62d3ac7", + "blockNumber": "0x708", + "blockTimestamp": "0x6a5dfc20", + "transactionHash": "0x5ec6a6ee2bd6d7a8837341121298c65d829616e78433af87a7bdce148959b535", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x97833955b02dfbf27590ebc66940a28f9fd12c8db3ecda6897428789b19343df038e6dc0a34dc13039fee91805bb4183b8102cf231938dfa7cb2d511a0393510", + "blockHash": "0xfd4c0568ac53ca9be296424ebc0a8a9257eeb0dbede657815db63792adf09be3", + "blockNumber": "0x70a", + "blockTimestamp": "0x6a5dfc20", + "transactionHash": "0xfddaa3542a2b5a3b2dc47164610c0cd130fc59619f86beaa1ce211992f6d0d57", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfd4c0568ac53ca9be296424ebc0a8a9257eeb0dbede657815db63792adf09be3", + "blockNumber": "0x70a", + "blockTimestamp": "0x6a5dfc20", + "transactionHash": "0xfddaa3542a2b5a3b2dc47164610c0cd130fc59619f86beaa1ce211992f6d0d57", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x45b397acdf13a3cdbfb009994c3d5fdcd92f8084d18739b530b33b18b7da511332094685aca2b46685235e480c8c486c4daf8e49d47a2ae5a1baafb4b81103c6", + "blockHash": "0xef46ecc26463455ff133f7df05aa86d3a22459e18e784e143410e6a0cc22794f", + "blockNumber": "0x70b", + "blockTimestamp": "0x6a5dfc21", + "transactionHash": "0xe1dd6c0295687c15db06b47deb9c7f47405be1e883ac594782d0c6e8f2b3e9c6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xef46ecc26463455ff133f7df05aa86d3a22459e18e784e143410e6a0cc22794f", + "blockNumber": "0x70b", + "blockTimestamp": "0x6a5dfc21", + "transactionHash": "0xe1dd6c0295687c15db06b47deb9c7f47405be1e883ac594782d0c6e8f2b3e9c6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x4d4dc957fda1cd2e1be45e99871d94c7cae7cf2c44491b80dba189b50442b971353cd67667af7538cad6a1373832d4ddfb9917630e574443d69118ec0c268b12", + "blockHash": "0x8df621414d36dac5e27ac388e6d9ebe565884b27be865db75ee3f053f04e351c", + "blockNumber": "0x70d", + "blockTimestamp": "0x6a5dfc22", + "transactionHash": "0xc5cd98f20052863be55e1e7812c68e2b30ee84027900b641ac31af3253f838db", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8df621414d36dac5e27ac388e6d9ebe565884b27be865db75ee3f053f04e351c", + "blockNumber": "0x70d", + "blockTimestamp": "0x6a5dfc22", + "transactionHash": "0xc5cd98f20052863be55e1e7812c68e2b30ee84027900b641ac31af3253f838db", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x910ad13860b394b4b71321cc9e1ab664ccd673cf1d4470a94ce9bd76a7403791fc7c255ac35606b10e5371dcd24f5d76d8345da4bfc4629e5672e2e834b96d3a", + "blockHash": "0x8d17703189962ef223bc2f6ef3f611466dd303c6d2694b411c078ea0473087b0", + "blockNumber": "0x70e", + "blockTimestamp": "0x6a5dfc23", + "transactionHash": "0x1242aad44edde79c0f2f8f8a8743efd3647e7dc3b997a44f3f9c0f3c43920fdf", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8d17703189962ef223bc2f6ef3f611466dd303c6d2694b411c078ea0473087b0", + "blockNumber": "0x70e", + "blockTimestamp": "0x6a5dfc23", + "transactionHash": "0x1242aad44edde79c0f2f8f8a8743efd3647e7dc3b997a44f3f9c0f3c43920fdf", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x7b830a41abea8de58eada23d635f7d491b64ea8c083613575e9e0c28922cc2b046b21fdd9e18eac5d851536943a793203a96c81e1eab771d760f8390bdc51d10", + "blockHash": "0x80882d8d0f3b14e9e05ddf1003cd749607445e85cdd585ab9742896cd9b9ff57", + "blockNumber": "0x710", + "blockTimestamp": "0x6a5dfc24", + "transactionHash": "0xde3e12e2ac13b7889caf0090a4c556f008bc795c2382c18a7d2d5ae87bb02366", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x80882d8d0f3b14e9e05ddf1003cd749607445e85cdd585ab9742896cd9b9ff57", + "blockNumber": "0x710", + "blockTimestamp": "0x6a5dfc24", + "transactionHash": "0xde3e12e2ac13b7889caf0090a4c556f008bc795c2382c18a7d2d5ae87bb02366", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xb9cc0f14088fcf4a5c6991536b7e431d3b514445c4ae7a327bd54ac8a37157509e8ce938a4c290d0cc3fb76a696f956a123a6f08b10c001d6bb2ab92edf24fac", + "blockHash": "0x98302f4af31cea854b1de649ea1ab736e39c5edb56e7530aba67b54c1c6393cf", + "blockNumber": "0x711", + "blockTimestamp": "0x6a5dfc25", + "transactionHash": "0x03bb5462a506bef7d0d4973640a048512a399b63caa4ea80f4bffaf8e7257f1a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x98302f4af31cea854b1de649ea1ab736e39c5edb56e7530aba67b54c1c6393cf", + "blockNumber": "0x711", + "blockTimestamp": "0x6a5dfc25", + "transactionHash": "0x03bb5462a506bef7d0d4973640a048512a399b63caa4ea80f4bffaf8e7257f1a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xf538ec55939602301149fb1c18cd7b1630f3dfdde37f29ed2ded9bde59e302ad1b073bbc943d749b58952142131323b1e0aa7d04ca0f9ea3f5b1173e83b6a4f5", + "blockHash": "0xdf6e76b9bd2adcc9f4666d2f87de98fc3fd7764f5ef035d71ce0f7b0c867ff39", + "blockNumber": "0x713", + "blockTimestamp": "0x6a5dfc25", + "transactionHash": "0x96f6a04689b5e8d8da70246a04f4aec138bc46e6e34b3c93e0f66aa976adc05c", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdf6e76b9bd2adcc9f4666d2f87de98fc3fd7764f5ef035d71ce0f7b0c867ff39", + "blockNumber": "0x713", + "blockTimestamp": "0x6a5dfc25", + "transactionHash": "0x96f6a04689b5e8d8da70246a04f4aec138bc46e6e34b3c93e0f66aa976adc05c", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x0cda7e8a56e4b7be9ef7cf4a4da4b78995469f404b34d762d627c95c637af81c4c90a7837492cf515cb98bbd77d10281d1f6c1db0f1a90a5569cfe7dbc3c77eb", + "blockHash": "0x9f146832c8a73d31e326764f525f623c2998990be0ae99ad36ad8e778cdec3b0", + "blockNumber": "0x714", + "blockTimestamp": "0x6a5dfc26", + "transactionHash": "0x970cd859d6630ad5f99031f0e99457fa23cc28f31d2e8202a67a8c166cab1df6", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9f146832c8a73d31e326764f525f623c2998990be0ae99ad36ad8e778cdec3b0", + "blockNumber": "0x714", + "blockTimestamp": "0x6a5dfc26", + "transactionHash": "0x970cd859d6630ad5f99031f0e99457fa23cc28f31d2e8202a67a8c166cab1df6", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x5f7cacf17c633fd0a0e07e66457a8ecb4a1629119fafb9910184a3b5056eeff752ee3af9df0deae61ee9feb77d0cd34db17c9759f4189badeb4539a60da9afe3", + "blockHash": "0x18fb146c0e5dabb8f8d7cd90282d557502c3d1ac402f2dc5c4bb2ec82b3400c3", + "blockNumber": "0x716", + "blockTimestamp": "0x6a5dfc27", + "transactionHash": "0xe825c1fd1e674e649e1b650b7428c09c7b85e9846e34c8d91dc85653441306cb", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x18fb146c0e5dabb8f8d7cd90282d557502c3d1ac402f2dc5c4bb2ec82b3400c3", + "blockNumber": "0x716", + "blockTimestamp": "0x6a5dfc27", + "transactionHash": "0xe825c1fd1e674e649e1b650b7428c09c7b85e9846e34c8d91dc85653441306cb", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x7448487d14f0f28d2005731902cf8cda6f10fb27bad8123e9aff3e7775509ea8b8551b4d8da55cbb4fbb2b87d6cf90026166199c4b82e1b55f52acd5bb96edf5", + "blockHash": "0xf00cd44898612b4cf6d8915323fdb820afa903ab11d16d01bcf28eab914b2925", + "blockNumber": "0x717", + "blockTimestamp": "0x6a5dfc28", + "transactionHash": "0x84058bbde97ce976f98ade8b9624ab9e07c22af03a2fc4cca499c7445a724789", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf00cd44898612b4cf6d8915323fdb820afa903ab11d16d01bcf28eab914b2925", + "blockNumber": "0x717", + "blockTimestamp": "0x6a5dfc28", + "transactionHash": "0x84058bbde97ce976f98ade8b9624ab9e07c22af03a2fc4cca499c7445a724789", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x81992a1e41bbe2b3323db4e958af618bfc91175f6a141d74add0b0617722db1c49face75bc7fc65155d9393d12aea4912c784f466ffea7b0df2439b7d900e3f5", + "blockHash": "0x5e33fd32bdac89d0a200ecfbe7480e49e941d2dec6f69ea3f1fabcdcd35d2b24", + "blockNumber": "0x719", + "blockTimestamp": "0x6a5dfc28", + "transactionHash": "0x6444ad773e08143fe1e590c49fce595c9a44112af3918056e72301cd64f1807a", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x5e33fd32bdac89d0a200ecfbe7480e49e941d2dec6f69ea3f1fabcdcd35d2b24", + "blockNumber": "0x719", + "blockTimestamp": "0x6a5dfc28", + "transactionHash": "0x6444ad773e08143fe1e590c49fce595c9a44112af3918056e72301cd64f1807a", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xfe40cc96ee83014ca63d91eee77cd0a994e8c2b4ea3af3c39660bbf72614518f520a9076d4012439c6f6436229427fc220a57324f2b3bde08bcc1477b526d80e", + "blockHash": "0x789a4d468bcd0f379bd4e0d63f0b275da501f835ad0e52b433089aba36c83fee", + "blockNumber": "0x71a", + "blockTimestamp": "0x6a5dfc29", + "transactionHash": "0x59fdaeff272f16d4ea98b46161bf5a8d322de1f427a8e227f63658bd90582962", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x789a4d468bcd0f379bd4e0d63f0b275da501f835ad0e52b433089aba36c83fee", + "blockNumber": "0x71a", + "blockTimestamp": "0x6a5dfc29", + "transactionHash": "0x59fdaeff272f16d4ea98b46161bf5a8d322de1f427a8e227f63658bd90582962", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xda1fa352431b692b629b8cd6e6b382a0df4d5adf6e5754f3f0c54f9cc4e206fbebb8862d87dd872bac167fc2b713d5811c95680f9e1f2fe738887ec04bc5b172", + "blockHash": "0x439f4fa137c1176da60f7eb9f7a5392689dfcd3a01efce57f5135c5861307dbe", + "blockNumber": "0x71c", + "blockTimestamp": "0x6a5dfc2a", + "transactionHash": "0xa81d5911c8a2a0ed995907673cb036f300c5cd3df5a3428bcbe9b414d22b7aab", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x439f4fa137c1176da60f7eb9f7a5392689dfcd3a01efce57f5135c5861307dbe", + "blockNumber": "0x71c", + "blockTimestamp": "0x6a5dfc2a", + "transactionHash": "0xa81d5911c8a2a0ed995907673cb036f300c5cd3df5a3428bcbe9b414d22b7aab", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x6221db9b478a83794fcb3043c6815e028fcf86b81fbec32f39c7f131b97ac901e43bbc08d0896551ae792557641425aea9e18d556222ad9a75ef057566a01f4d", + "blockHash": "0x21695888f621129bb1b6af044c6d751c15258e771bcbcd55d0381a057f0c145b", + "blockNumber": "0x71d", + "blockTimestamp": "0x6a5dfc2b", + "transactionHash": "0xd973847f48a44425bef6fb79a54e794f5d9b2a7f3ae4191110cd0f8076f5594b", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x21695888f621129bb1b6af044c6d751c15258e771bcbcd55d0381a057f0c145b", + "blockNumber": "0x71d", + "blockTimestamp": "0x6a5dfc2b", + "transactionHash": "0xd973847f48a44425bef6fb79a54e794f5d9b2a7f3ae4191110cd0f8076f5594b", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xe47e849abe490f76802b23778e07c64e57dad971f543d9879b7938c845fda1a5256639d4b0f9a56de6ff22d255051419b9a597026ea812f496cc023564d415b1", + "blockHash": "0xd47dd2d58d4808dc47b9da245b8b5f06f48981dea82061584067e2db8cc53217", + "blockNumber": "0x71f", + "blockTimestamp": "0x6a5dfc2b", + "transactionHash": "0xcce37515920adad74e4b922dc5c36e2f2fb4bddeab6d2369dc42a6180e8ab2ca", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xd47dd2d58d4808dc47b9da245b8b5f06f48981dea82061584067e2db8cc53217", + "blockNumber": "0x71f", + "blockTimestamp": "0x6a5dfc2b", + "transactionHash": "0xcce37515920adad74e4b922dc5c36e2f2fb4bddeab6d2369dc42a6180e8ab2ca", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0x41bbe06287a5b7ffa222887e2ac9faf3e3ee49110fca8492e9330795934b145c3949d0218e956a1dd44583c0fc49e5e29084f74ae374b80486d2afe3121e2fdd", + "blockHash": "0x0abff8b6918c0b106e70f7947f9c865c1f09238991774ee5be144fa1bcb535b5", + "blockNumber": "0x720", + "blockTimestamp": "0x6a5dfc2d", + "transactionHash": "0x0143b6d76cf9ed9d3a47b8e36dcf00e955c2e589540a2f48915dbbe3fd7a6bb5", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0abff8b6918c0b106e70f7947f9c865c1f09238991774ee5be144fa1bcb535b5", + "blockNumber": "0x720", + "blockTimestamp": "0x6a5dfc2d", + "transactionHash": "0x0143b6d76cf9ed9d3a47b8e36dcf00e955c2e589540a2f48915dbbe3fd7a6bb5", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0xbc14010e647cf07dd4f48df2f806ec59932be73b1b969e6dff6fa55e805a1cbc", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e" + ], + "data": "0xe83853978b5e41331b51523acbd8c2e719c34c288e56933e08afd3f9b2614949da5b5a1da7269ad75d8319cae3e09da9f6548d7dc4dbbc407487d70a1c6803f1", + "blockHash": "0x306f62d501fa569bdcfb78b61322152f0462c2bce17010262d918efd46543a8c", + "blockNumber": "0x722", + "blockTimestamp": "0x6a5dfc2d", + "transactionHash": "0x92a6d2263e7b0e29432a29cb6d3092973933a53c646c74f58b8e92f5cb360abe", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009766000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x306f62d501fa569bdcfb78b61322152f0462c2bce17010262d918efd46543a8c", + "blockNumber": "0x722", + "blockTimestamp": "0x6a5dfc2d", + "transactionHash": "0x92a6d2263e7b0e29432a29cb6d3092973933a53c646c74f58b8e92f5cb360abe", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000009e51800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3031e62baaeb127cbd95840d20f098ce627453759b32bf482058ffee2622e235", + "blockNumber": "0x723", + "blockTimestamp": "0x6a5dfc2e", + "transactionHash": "0x91e7981efd834e1e11cfbd47e5fe1d30076837992fa29bdc14f770113b76e305", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0xc9cbfcbc344f3b1c795f11f0f2777945de91c3edfce00ca03f00b826ba846b6e", + "0x970b2080348c1b551aa94fb50df0ad93ee39130f61bbca9ef3113b5460a585b0", + "0x06653978d701eed8529ab87815e2fe83792eb0758689f1ff475654ad06348aaa" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "blockHash": "0xfaebd56c870d07e33d937b609de9e06f52b932f359ee80ac6129810988487a54", + "blockNumber": "0x725", + "blockTimestamp": "0x6a5dfc2f", + "transactionHash": "0x2e4b559165dcad6b9bcb120d591a6931fa74baf68db098646d095dbff1c3683e", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x09291063ecd2f46c50647dae4c98733a7d641ba7", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000003147e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfaebd56c870d07e33d937b609de9e06f52b932f359ee80ac6129810988487a54", + "blockNumber": "0x725", + "blockTimestamp": "0x6a5dfc2f", + "transactionHash": "0x2e4b559165dcad6b9bcb120d591a6931fa74baf68db098646d095dbff1c3683e", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x05cd5e567b92bdd7cd7d818f0439776d8999340d2614ba12966fde781b492420", + "0x033a726bc0f8383ace5f18326de1013035d88146692f5ce9d0a107e02a6502e4", + "0x1be220a263240d2a80bfecce9776b351398491beedbd1858a1d1c301f38dd2af" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xba2465e17a50a7940bf38118645efdb5915aeca34c9f6819dadf6304c09925f6", + "blockNumber": "0x826", + "blockTimestamp": "0x6a5dfc35", + "transactionHash": "0x893bef89cff2c86a444e86e8fe7f9ed044d998710dcf76b52fcb520eac2a7415", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x1259d72da50ce2a1ee137670989ef02ffd6862da", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001bffd800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xba2465e17a50a7940bf38118645efdb5915aeca34c9f6819dadf6304c09925f6", + "blockNumber": "0x826", + "blockTimestamp": "0x6a5dfc35", + "transactionHash": "0x893bef89cff2c86a444e86e8fe7f9ed044d998710dcf76b52fcb520eac2a7415", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x1d3ae066e466a0203dcc05b06daeba9922750bcf99aa7837a92123fca1287406", + "0x7cb9ae66539e8f657e1757b3878e7706a86dd183757b74b82135568b59aa4bf0", + "0x5f4534974abdaba24f59aafe7ad50b9280357bd8da67d1bc49f056d2db0ac3a0", + "0x8f0345469ea416ccb397e3245695bb40d6fb220f6508edcd128a020b761baf36" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x8d0ddd24479efda656dead183983f4434dff7eff729b1fd7864b8a83521dbd74", + "blockNumber": "0x827", + "blockTimestamp": "0x6a5dfc36", + "transactionHash": "0xabc7c4c76442d701cf3b25f26427ed690d52a1b777da376edd8086e900412169", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0xfb5043a8c6449c81ddb8570f56a9230d74a63b9a", + "topics": [ + "0x938a52b87ed1353360e17d203a73343c4e92b6a9e9a0b50d0e38df31fbf14219", + "0x00000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000001ba9c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8d0ddd24479efda656dead183983f4434dff7eff729b1fd7864b8a83521dbd74", + "blockNumber": "0x827", + "blockTimestamp": "0x6a5dfc36", + "transactionHash": "0xabc7c4c76442d701cf3b25f26427ed690d52a1b777da376edd8086e900412169", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0x13bd4fdfe8d8a96c44e1f8c899cde8f2ae549c60b4768631f1a88541f85bec62", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ], + "data": "0x01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6", + "blockHash": "0x5eb04af1a847aeaf9e00c429ac8bb4ddf95919c441c4769e34ae57005b87cb1a", + "blockNumber": "0x828", + "blockTimestamp": "0x6a5dfc37", + "transactionHash": "0x2163afdf57a0dfa169fb677482cd30aa2f812e090ebb0e303b0244547b3a39ac", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x64f6cf454348f891e837eddc99be67ea98c64602", + "topics": [ + "0x66b8e5c50b708b71a6f155375a4613dd12971db692c5931e7cb0701ee7add6ac" + ], + "data": "0x000000000000000000000000d215554dcd3bb248d79c9f88875af82c294e71f5", + "blockHash": "0x85680547374b5deb76f5a9224e5b6a0cde711a2358b4edee594239799f1d5b2c", + "blockNumber": "0x829", + "blockTimestamp": "0x6a5dfc37", + "transactionHash": "0x57ba7fec9cc203ec334b729bc87d919c58c1b108b688c66933bc0ec60213670d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x669af1d7ff26a5cfd48af4668831299f31e4ad1f", + "topics": [ + "0xa91d0b68c00a132585cc08007b46ff5f0abc622f5286b5701149b33784764ced", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000d01c8e45036ac17f2224aeecbca3233b540336caf26558416f31f914c371a55fa0a162946e56158bac0673e6dd3bdfdc1e4a0e7744a120fdb640050c8d7abe1c6000000000000000000000000d215554dcd3bb248d79c9f88875af82c294e71f5", + "blockHash": "0x85680547374b5deb76f5a9224e5b6a0cde711a2358b4edee594239799f1d5b2c", + "blockNumber": "0x829", + "blockTimestamp": "0x6a5dfc37", + "transactionHash": "0x57ba7fec9cc203ec334b729bc87d919c58c1b108b688c66933bc0ec60213670d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ] +} \ No newline at end of file diff --git a/cartesi-rollups/node/tests/fixtures/engine_echo.json b/cartesi-rollups/node/tests/fixtures/engine_echo.json new file mode 100644 index 000000000..ee21a3f3d --- /dev/null +++ b/cartesi-rollups/node/tests/fixtures/engine_echo.json @@ -0,0 +1,6 @@ +{ + "epoch_root_r44_h48": "0x204bf1bd7f4002cd3e326fe5482739fb5454c225e8753e8d0c1db485e3f2e550", + "mid_stride_r27_h10": "0x84b522b02b5c97ed4805afd903af2aea3fe3599daaaaa7f2bdd5c15613a1967c", + "template_hash": "0xe22edfbd343637e72852bffee3c88db8ddcef0a3dc30e4c00e2e47e824346821", + "uarch_span_r0_h20": "0x4d4db053d023a4aed8677854d5078437abfb284f283afd9d314f89fbff638e02" +} \ No newline at end of file diff --git a/cartesi-rollups/node/tests/tournament_fold.rs b/cartesi-rollups/node/tests/tournament_fold.rs new file mode 100644 index 000000000..44c9ba011 --- /dev/null +++ b/cartesi-rollups/node/tests/tournament_fold.rs @@ -0,0 +1,319 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! The fold against the chain recordings (workstream 5, phase 1): +//! raw devnet logs captured after real e2e disputes, decoded through +//! the same bindings the production fetcher uses, folded from +//! genesis. The scenarios' known shapes are the oracle: what +//! tournaments a dispute spawns, how their matches end, and which +//! commitments took part are facts of the recorded run that the fold +//! must reproduce from the log stream alone. No machine image and no +//! chain required. + +use alloy::{primitives::Address, rpc::types::Log, sol_types::SolEvent}; +use cartesi_dave_contracts::dave_consensus::DaveConsensus; +use cartesi_rollups_prt_node::tournament::fold::{ + EventKind, Fold, MatchDeletionReason, TournamentEvent, WinnerCommitment, decode_event, +}; +use std::collections::BTreeMap; +use std::path::PathBuf; + +fn recording(name: &str) -> Option> { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/chain-recordings") + .join(name); + if !path.exists() { + return None; + } + let raw: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()) + .expect("recording must be valid JSON"); + let logs = raw["logs"] + .as_array() + .expect("recording must carry a log array") + .iter() + .map(|log| serde_json::from_value(log.clone()).expect("log must decode as an RPC log")) + .collect(); + Some(logs) +} + +/// The roots the consensus sealed, in epoch order, straight from the +/// recorded EpochSealed events. +fn sealed_tournaments(logs: &[Log]) -> Vec<(u64, Address)> { + logs.iter() + .filter_map(|log| { + DaveConsensus::EpochSealed::decode_log(&log.inner) + .ok() + .map(|e| (u64::try_from(e.epochNumber).unwrap(), e.tournament)) + }) + .collect() +} + +/// Folds one root tournament's dispute out of the whole-chain stream. +/// Discovery is inline: logs arrive in block order, and an inner +/// tournament's logs can only follow the NewInnerTournament that +/// names it, so a single chronological pass suffices. +fn fold_dispute(logs: &[Log], root: Address) -> (Fold, Vec) { + let mut fold = Fold::new(root); + let mut applied = Vec::new(); + for log in logs { + let Some(event) = decode_event(log).expect("recorded logs must decode") else { + continue; + }; + if fold.tournament(&event.tournament).is_none() { + continue; // another epoch's tournament, or a foreign contract + } + fold.apply(&event).expect("recorded stream must fold"); + applied.push(event); + } + (fold, applied) +} + +/// echo_simple: one full dispute on epoch 1 - the honest node against +/// one sybil, descending all three levels to a leaf-step elimination. +#[test] +fn fold_reproduces_the_echo_simple_dispute() { + let Some(logs) = recording("echo_simple.json") else { + panic!("echo_simple.json is a committed fixture and must exist"); + }; + + let sealed = sealed_tournaments(&logs); + assert!( + sealed.len() >= 2, + "the scenario seals epoch 0 and the disputed epoch 1" + ); + let root = sealed + .iter() + .find(|(epoch, _)| *epoch == 1) + .expect("epoch 1 is the disputed epoch") + .1; + + let (fold, applied) = fold_dispute(&logs, root); + assert!(!applied.is_empty(), "the dispute left events to fold"); + + // The dispute descends all three levels: the root plus two inner + // tournaments, each spawned by a sealed match of its parent. + let tournaments: Vec<_> = fold.tournaments().collect(); + assert_eq!(tournaments.len(), 3, "three levels of tournament"); + let levels: Vec = tournaments.iter().map(|t| t.level).collect(); + assert_eq!(levels, vec![0, 1, 2]); + + for (i, t) in tournaments.iter().enumerate() { + assert_eq!( + t.commitments.len(), + 2, + "honest and sybil at level {}", + t.level + ); + assert_eq!(t.matches.len(), 1, "one match at level {}", t.level); + let m = &t.matches[0]; + + // Parent linkage: each inner tournament hangs off its + // parent's single match. + if i > 0 { + let (parent_address, match_id_hash) = t.parent.expect("inner has a parent"); + assert_eq!(parent_address, tournaments[i - 1].address); + assert_eq!(match_id_hash, tournaments[i - 1].matches[0].id.hash()); + } + + // Every commitment saw the match. + for c in t.commitments.values() { + assert_eq!(c.latest_match, Some(0)); + } + + // Lifecycle: the two non-leaf matches sealed into inner + // tournaments and were closed by them; the leaf match died by + // an on-chain step. Every deletion crowned a winner. + let (reason, winner) = m.deleted.expect("all matches resolve in a settled epoch"); + if t.level < 2 { + assert_eq!(m.inner_tournament, Some(tournaments[i + 1].address)); + assert_eq!(reason, MatchDeletionReason::ChildTournament); + } else { + assert_eq!(m.inner_tournament, None); + assert_eq!(reason, MatchDeletionReason::Step); + } + assert_ne!(winner, WinnerCommitment::Neither); + assert!(m.advances > 0, "bisection advanced at level {}", t.level); + } + + // The same commitment pair fights the level-0 match that the + // epoch's join events introduced, and final states ride the + // joins: every joined commitment carries one. + let root_t = fold.tournament(&root).unwrap(); + for c in root_t.commitments.values() { + assert_ne!(c.final_state.slice(), [0u8; 32]); + } + + // Event-count audit: everything decodable in the dispute's + // address set was applied, and the vocabulary saw every kind. + let mut kinds: BTreeMap<&'static str, usize> = BTreeMap::new(); + for e in &applied { + *kinds + .entry(match e.kind { + EventKind::CommitmentJoined { .. } => "joined", + EventKind::MatchCreated { .. } => "created", + EventKind::MatchAdvanced { .. } => "advanced", + EventKind::MatchDeleted { .. } => "deleted", + EventKind::NewInnerTournament { .. } => "inner", + }) + .or_default() += 1; + } + assert_eq!(kinds["joined"], 6, "two commitments per level"); + assert_eq!(kinds["created"], 3); + assert_eq!(kinds["deleted"], 3); + assert_eq!(kinds["inner"], 2); + assert!(kinds["advanced"] >= 3); + println!("echo_simple fold: {kinds:?}"); +} + +/// The multi-dispute recording (honeypot stf_all): five epochs, each +/// steering its dispute onto a different on-chain transition shape. +/// Every sealed epoch's dispute must fold clean; disputed epochs +/// resolve every match and crown winners at the leaf by steps. +#[test] +fn fold_reproduces_the_stf_all_disputes() { + let Some(logs) = recording("multilevel_stf.json") else { + eprintln!("skipping: multilevel_stf.json not recorded yet"); + return; + }; + + let sealed = sealed_tournaments(&logs); + assert!(sealed.len() >= 4, "stf_all seals several epochs"); + + let mut disputed = 0; + for (epoch, root) in &sealed { + let (fold, applied) = fold_dispute(&logs, *root); + if applied.is_empty() { + continue; // an epoch this recording never disputed or joined + } + + for t in fold.tournaments() { + for m in &t.matches { + if let Some((reason, winner)) = m.deleted { + assert_ne!( + winner, + WinnerCommitment::Neither, + "every recorded deletion crowned a winner (epoch {epoch})" + ); + match reason { + MatchDeletionReason::ChildTournament => { + assert!(m.inner_tournament.is_some()) + } + MatchDeletionReason::Step | MatchDeletionReason::Timeout => { + assert!(m.inner_tournament.is_none()) + } + } + } + } + } + + let t: Vec<_> = fold.tournaments().collect(); + if t.len() > 1 { + disputed += 1; + // A dispute that spawned inners fought them to the leaf. + assert_eq!(t.len(), 3, "disputes descend all levels (epoch {epoch})"); + assert!( + t.last() + .unwrap() + .matches + .iter() + .any(|m| m.deleted.is_some()), + "the leaf level resolved (epoch {epoch})" + ); + } + println!( + "epoch {epoch}: {} tournaments, {} events", + t.len(), + applied.len() + ); + } + assert!(disputed >= 4, "stf_all disputes at least four epochs"); +} + +/// multi_sybil: four commitments in one root tournament, two matches +/// live at the same time (the permissionless shape no other fixture +/// carries), and a silent sybil whose match dies by a REAL on-chain +/// timeout - the deletion reason every other test builds +/// synthetically. Captured from the multi_sybil e2e scenario +/// (RECORD_CHAIN_FIXTURE); regeneration is a conscious, reviewable +/// act like every fixture here. +#[test] +fn fold_reproduces_the_multi_sybil_dispute() { + let Some(logs) = recording("multi_sybil.json") else { + panic!("multi_sybil.json is a committed fixture and must exist"); + }; + + let sealed = sealed_tournaments(&logs); + let root = sealed + .iter() + .find(|(epoch, _)| *epoch == 1) + .expect("epoch 1 is the disputed epoch") + .1; + + let (fold, applied) = fold_dispute(&logs, root); + assert!(!applied.is_empty(), "the dispute left events to fold"); + + let root_t = fold.tournament(&root).expect("root tournament folds"); + assert_eq!( + root_t.commitments.len(), + 4, + "honest plus three sybils joined the root" + ); + assert!( + root_t.matches.len() >= 3, + "four commitments pair into at least three matches over the bracket" + ); + + // Concurrency, from the event stream itself: a second match is + // created in the root before the first one is deleted. + let mut created = 0usize; + let mut overlapped = false; + for event in &applied { + if event.tournament != root { + continue; + } + match &event.kind { + EventKind::MatchCreated { .. } => { + created += 1; + if created >= 2 { + overlapped = true; + } + } + EventKind::MatchDeleted { .. } => { + if created >= 2 { + overlapped = true; + } + created = created.saturating_sub(1); + } + _ => {} + } + } + assert!( + overlapped, + "two matches must have been live simultaneously in the root" + ); + + // The silent sybil's match died by a real timeout, decoded from a + // real chain log (the enum-order pin the synthetic tests bypass). + let timeout_deletions = applied + .iter() + .filter(|e| { + matches!( + e.kind, + EventKind::MatchDeleted { + reason: MatchDeletionReason::Timeout, + .. + } + ) + }) + .count(); + assert!( + timeout_deletions >= 1, + "at least one match deletion carries the Timeout reason" + ); + + // The dispute still descended to a leaf resolution somewhere. + assert!( + fold.tournaments().count() >= 2, + "the active matches sealed into inner tournaments" + ); +} diff --git a/common-rs/.gitignore b/common-rs/.gitignore deleted file mode 100644 index 03314f77b..000000000 --- a/common-rs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock diff --git a/common-rs/README.md b/common-rs/README.md deleted file mode 100644 index 7757b151b..000000000 --- a/common-rs/README.md +++ /dev/null @@ -1 +0,0 @@ -# Dave Common Library \ No newline at end of file diff --git a/common-rs/arithmetic/Cargo.toml b/common-rs/arithmetic/Cargo.toml deleted file mode 100644 index f8229b3bc..000000000 --- a/common-rs/arithmetic/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "cartesi-dave-arithmetic" -version = { workspace = true } - -authors = { workspace = true } -description = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -readme = { workspace = true } -repository = { workspace = true } diff --git a/common-rs/kms/.dockerignore b/common-rs/kms/.dockerignore deleted file mode 100644 index 5ba0054ae..000000000 --- a/common-rs/kms/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -volume/ \ No newline at end of file diff --git a/common-rs/kms/.gitignore b/common-rs/kms/.gitignore deleted file mode 100644 index 5ba0054ae..000000000 --- a/common-rs/kms/.gitignore +++ /dev/null @@ -1 +0,0 @@ -volume/ \ No newline at end of file diff --git a/common-rs/kms/Cargo.toml b/common-rs/kms/Cargo.toml deleted file mode 100644 index fe82d7437..000000000 --- a/common-rs/kms/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cartesi-dave-kms" -version.workspace = true -authors.workspace = true -description.workspace = true -edition.workspace = true -homepage.workspace = true -license-file.workspace = true -readme.workspace = true -repository.workspace = true - -[dependencies] -aws-config = { version = "1.6", default-features = false, features = [ - "rustls", - "rt-tokio", -] } -aws-sdk-kms = { version = "1.65", default-features = false, features = [ - "rustls", - "rt-tokio", -] } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -alloy = { workspace = true, features = ["signer-aws"] } -testcontainers-modules = { version = "0.13.0", default-features = false, features = [ - "localstack", -] } -anyhow = { workspace = true } - -[dev-dependencies] -lazy_static = { workspace = true } diff --git a/common-rs/kms/README.md b/common-rs/kms/README.md deleted file mode 100644 index 5c8639bff..000000000 --- a/common-rs/kms/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# KMS Signer - -In [localstack](https://docs.localstack.cloud/user-guide/aws/kms/) for create a KMS key you need this command using [AWS LOCAL](https://github.com/localstack/awscli-local): - -```bash -awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 -``` - -For list keys: -```bash -awslocal kms list-keys -``` - -For more details: -```bash -awslocal kms describe-key --key-id KEY_HERE -``` - -Run command from PRT Compute: - -```bash -cargo run -p cartesi-prt-compute -- \ - --aws-access-key-id KEY_ID \ - --aws-secret-access-key SECRET_ACCESS_KEY \ - --aws-endpoint-url ENDPOINT \ - --aws-region REGION \ - --web3-chain-id CHAIN_ID -``` diff --git a/common-rs/kms/aws.sh b/common-rs/kms/aws.sh deleted file mode 100755 index 3ba27ac28..000000000 --- a/common-rs/kms/aws.sh +++ /dev/null @@ -1,22 +0,0 @@ -set -eux - -curl -X POST $AWS_ENDPOINT_URL \ - -H "Content-Type: application/x-amz-json-1.1" \ - -H "X-Amz-Target: TrentService.CreateKey" \ - -d '{ "KeyUsage": "SIGN_VERIFY", "KeySpec": "ECC_SECG_P256K1" }' | jq -C - -KEY_ID=$(curl -X POST $AWS_ENDPOINT_URL \ - -H "Content-Type: application/x-amz-json-1.1" \ - -H "X-Amz-Target: TrentService.ListKeys" \ - -d '{}' | jq -r ".Keys[0].KeyId") - -if [ -n "$KEY_ID" ]; then - echo "export AWS_KMS_KEY_ID=\"$KEY_ID\"" | tee -a ~/.bashrc -else - echo "No Key ID found." -fi - -# Store AWS key ID in an environment variable for the current session -export AWS_KMS_KEY_ID="$KEY_ID" - -just test-rollups-echo \ No newline at end of file diff --git a/common-rs/kms/compose.yaml b/common-rs/kms/compose.yaml deleted file mode 100644 index 3a551fe10..000000000 --- a/common-rs/kms/compose.yaml +++ /dev/null @@ -1,31 +0,0 @@ -services: - localstack: - container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}" - image: localstack/localstack:4.1.1 - ports: - - "127.0.0.1:4566:4566" # LocalStack Gateway - - "127.0.0.1:4510-4559:4510-4559" # external services port range - environment: - # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ - - DEBUG=${DEBUG:-0} - - SERVICES=kms - volumes: - - localstackdata:/var/lib/localstack - cartesi-rollups-prt-node: - build: - context: ../.. - dockerfile: test/Dockerfile - command: [ "bash", "-c", "/dave/aws.sh" ] - volumes: - - ./aws.sh:/dave/aws.sh:ro - environment: - # AWS credentials - - AWS_ACCESS_KEY_ID=ANOTREAL - - AWS_SECRET_ACCESS_KEY=notrealrnrELgWzOk3IfjzDKtFBhDby - - AWS_ENDPOINT_URL=http://localstack:4566 - - AWS_REGION=us-east-1 - depends_on: - localstack: - condition: service_healthy -volumes: - localstackdata: diff --git a/common-rs/merkle/Cargo.toml b/common-rs/merkle/Cargo.toml deleted file mode 100644 index b389125f3..000000000 --- a/common-rs/merkle/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "cartesi-dave-merkle" -version = { workspace = true } - -authors = { workspace = true } -description = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -readme = { workspace = true } -repository = { workspace = true } - -[dependencies] -alloy = { workspace = true, features = ["sol-types"] } -ruint = { workspace = true } - -hex = "0.4" -tiny-keccak = { workspace = true } -thiserror = { workspace = true } diff --git a/machine/rust-bindings/cartesi-machine-sys/build.rs b/machine/rust-bindings/cartesi-machine-sys/build.rs index bb922419b..be3040baa 100644 --- a/machine/rust-bindings/cartesi-machine-sys/build.rs +++ b/machine/rust-bindings/cartesi-machine-sys/build.rs @@ -12,20 +12,30 @@ fn main() { .canonicalize() .expect("cannot canonicalize path"); - // Clean build artifacts and start from scratch - // clean(&machine_dir_path); + // Where libcartesi comes from, in order of precedence: + // 1. the `external_cartesi` feature (LIBCARTESI_PATH, or the submodule's + // src/ if unset); + // 2. a LIBCARTESI_PATH in the environment (e.g. exported by the nix + // devshell), even without the feature; + // 3. fallback: build the emulator from the `machine/emulator` submodule. + // The fallback is what lets these bindings track an arbitrary emulator + // commit: unset LIBCARTESI_PATH (or point it at the submodule's src/) + // and cargo builds whatever the submodule is checked out at. + let external_lib_dir = env::var("LIBCARTESI_PATH").ok().map(PathBuf::from); - // tell Cargo where to look for libraries cfg_if::cfg_if! { if #[cfg(feature = "external_cartesi")] { - let libpath = - env::var("LIBCARTESI_PATH") - .map(PathBuf::from) - .unwrap_or_else(|_| machine_dir_path.join("src")); - println!("cargo:rustc-link-search={}", libpath.to_str().unwrap()); + let libpath = external_lib_dir + .clone() + .unwrap_or_else(|| machine_dir_path.join("src")); + link_external(&libpath, &out_path); } else { - build_cm::build(&machine_dir_path, &out_path); - println!("cargo:rustc-link-search={}", out_path.to_str().unwrap()); + if let Some(libpath) = external_lib_dir.as_ref() { + link_external(libpath, &out_path); + } else { + build_cm::build(&machine_dir_path, &out_path); + println!("cargo:rustc-link-search={}", out_path.to_str().unwrap()); + } } } @@ -65,19 +75,19 @@ fn main() { // Generate bindings // - // find headers - #[allow(clippy::needless_late_init)] - let include_path; - cfg_if::cfg_if! { - if #[cfg(feature = "external_cartesi")] { - include_path = env::var("INCLUDECARTESI_PATH") - .map(PathBuf::from) - .unwrap_or_else(|_| machine_dir_path.join("src")); - - } else { - include_path = machine_dir_path.join("src"); - } - }; + // Find headers, mirroring the library precedence: INCLUDECARTESI_PATH + // wins; an external lib dir implies its sibling include/cartesi-machine + // (the emulator's install layout); otherwise the submodule sources. + let include_path = env::var("INCLUDECARTESI_PATH") + .map(PathBuf::from) + .ok() + .or_else(|| { + external_lib_dir + .as_ref() + .and_then(|lib| lib.parent().map(|p| p.join("include/cartesi-machine"))) + .filter(|p| p.join("machine-c-api.h").exists()) + }) + .unwrap_or_else(|| machine_dir_path.join("src")); // generate machine api let machine_bindings = bindgen::Builder::default() @@ -103,6 +113,38 @@ fn main() { ); println!("cargo::rerun-if-env-changed=UARCH_PRISTINE_HASH_PATH"); println!("cargo::rerun-if-env-changed=UARCH_PRISTINE_RAM_PATH"); + println!("cargo::rerun-if-env-changed=LIBCARTESI_PATH"); + println!("cargo::rerun-if-env-changed=INCLUDECARTESI_PATH"); +} + +// Stage the external static archives into OUT_DIR and search only there. +// Searching the provider's lib dir directly is not safe: it usually also +// contains libcartesi dylibs, and ld64 prefers a dylib over an archive +// even under rustc's `static=` modifier, producing binaries that need an +// rpath into the provider's tree at runtime. +fn link_external(libdir: &std::path::Path, out_path: &std::path::Path) { + stage_archive(&libdir.join("libcartesi.a"), out_path); + + // Only present in installs built with the jsonrpc machine; required + // just for the `remote_machine` feature. + let jsonrpc = libdir.join("libcartesi_jsonrpc.a"); + if jsonrpc.exists() { + stage_archive(&jsonrpc, out_path); + } + + println!("cargo:rustc-link-search={}", out_path.to_str().unwrap()); +} + +fn stage_archive(archive: &std::path::Path, out_path: &std::path::Path) { + let staged = out_path.join(archive.file_name().unwrap()); + // fs::copy preserves the source mode; a nix-store source stages a + // read-only copy that the next build-script run cannot overwrite. + if staged.exists() { + std::fs::remove_file(&staged) + .unwrap_or_else(|e| panic!("failed to unstage `{}`: {e}", staged.display())); + } + std::fs::copy(archive, &staged) + .unwrap_or_else(|e| panic!("failed to copy `{}` to OUT_DIR: {e}", archive.display())); } #[cfg(not(feature = "external_cartesi"))] diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index 0905483ba..3dce4dbf1 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -315,7 +315,9 @@ pub type VirtIOHostfwdArray = Vec; /// `to_json(virtio_device_config)` implementation. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "kebab-case", deny_unknown_fields)] +#[derive(Default)] pub enum VirtIODeviceConfig { + #[default] Console, P9fs { tag: String, @@ -331,12 +333,6 @@ pub enum VirtIODeviceConfig { }, } -impl Default for VirtIODeviceConfig { - fn default() -> Self { - VirtIODeviceConfig::Console - } -} - pub type VirtIOConfigs = Vec; // --------------------------------------------------------------------------- diff --git a/machine/rust-bindings/cartesi-machine/src/constants.rs b/machine/rust-bindings/cartesi-machine/src/constants.rs index 92403e416..0b91ffe56 100644 --- a/machine/rust-bindings/cartesi-machine/src/constants.rs +++ b/machine/rust-bindings/cartesi-machine/src/constants.rs @@ -11,10 +11,10 @@ pub mod machine { use cartesi_machine_sys::*; // pub const CYCLE_MAX: u64 = CM_MCYCLE_MAX as u64; - pub const HASH_SIZE: u32 = CM_HASH_SIZE as u32; - pub const HASH_TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; - pub const HASH_TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; - pub const HASH_TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; + pub const HASH_SIZE: u32 = CM_HASH_SIZE; + pub const HASH_TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE; + pub const HASH_TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE; + pub const HASH_TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE; } pub mod ar { diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 4e45f9f6c..5592c8036 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -12,7 +12,7 @@ use crate::{ constants, error::{MachineError, MachineResult as Result}, types::{ - BreakReason, Hash, LogType, Register, UArchBreakReason, + BreakReason, Hash, LogType, Register, SharingMode, UArchBreakReason, access_proof::AccessLog, cmio::{CmioRequest, CmioResponseReason}, memory_proof::Proof, @@ -153,8 +153,28 @@ impl Machine { Ok(Self { machine }) } - /// Loads a new machine instance from a previously stored directory. + /// Loads a new machine instance from a previously stored directory, + /// with the config's own per-range sharing (fully private in + /// practice: mutations stay in memory). pub fn load(dir: &Path, runtime_config: &RuntimeConfig) -> Result { + Self::load_with_sharing(dir, runtime_config, SharingMode::Config) + } + + /// Loads a stored machine with an explicit sharing mode. + /// + /// With [`SharingMode::All`] the machine mutates the directory in + /// place, and the directory remains a valid stored machine after + /// drop without any explicit flush: dirtiness is recorded in the + /// persisted dirty-page sidecars and the next `root_hash` rehashes + /// exactly the dirty set. Calling [`Machine::root_hash`] before + /// drop leaves the on-disk hash tree exact. Note the lifetime + /// exclusive lock: the directory cannot be cloned or re-loaded + /// shared until this machine is dropped. + pub fn load_with_sharing( + dir: &Path, + runtime_config: &RuntimeConfig, + sharing: SharingMode, + ) -> Result { let dir_cstr = path_to_cstring(dir)?; let runtime_config_json = serialize_to_json!(&runtime_config); @@ -163,7 +183,7 @@ impl Machine { cartesi_machine_sys::cm_load_new( dir_cstr.as_ptr(), runtime_config_json.as_ptr(), - cartesi_machine_sys::CM_SHARING_CONFIG, + sharing.into(), &mut machine, ) }; @@ -172,6 +192,52 @@ impl Machine { Ok(Self { machine }) } + /// Clones a stored machine directory without loading it: read-only + /// files hard-link, writable files reflink on CoW filesystems, and + /// either falls back to a sparse-aware copy where unsupported - so + /// the clone is cheap where the filesystem cooperates and correct + /// everywhere. Refuses an existing destination. Fails while the + /// source is loaded with [`SharingMode::All`] (the load holds the + /// lock); drop that machine first. + pub fn clone_stored(from_dir: &Path, to_dir: &Path) -> Result<()> { + let from_cstr = path_to_cstring(from_dir)?; + let to_cstr = path_to_cstring(to_dir)?; + let empty = Self::new_empty()?; + let err_code = unsafe { + cartesi_machine_sys::cm_clone_stored( + empty.machine, + from_cstr.as_ptr(), + to_cstr.as_ptr(), + ) + }; + check_err!(err_code)?; + + Ok(()) + } + + /// Removes a stored machine directory. The directory must not be + /// in use; on failure some files may remain. + pub fn remove_stored(dir: &Path) -> Result<()> { + let dir_cstr = path_to_cstring(dir)?; + let empty = Self::new_empty()?; + let err_code = + unsafe { cartesi_machine_sys::cm_remove_stored(empty.machine, dir_cstr.as_ptr()) }; + check_err!(err_code)?; + + Ok(()) + } + + /// An empty local machine object (`cm_new`): holds no instance, + /// exists only to dispatch stored-directory operations. The C API + /// rejects a NULL object despite its header's claim. + fn new_empty() -> Result { + let mut machine: *mut cartesi_machine_sys::cm_machine = ptr::null_mut(); + let err_code = unsafe { cartesi_machine_sys::cm_new(&mut machine) }; + check_err!(err_code)?; + + Ok(Self { machine }) + } + /// Stores a machine instance to a directory, serializing its entire state. /// Uses CM_SHARING_ALL so that the current machine state is written for all /// address ranges (required when storing in-memory machines that have no @@ -1090,4 +1156,112 @@ mod tests { Ok(()) } + + /// The CoW clone loop's foundational facts (docs/plans/snapshots.md): + /// a clone loaded with SharingMode::All advances on disk and remains + /// a valid stored machine after drop, with the hash sidecars exact + /// when root_hash ran before the drop, and self-healing from the + /// recorded dirty pages when it did not (the crash shape). Both + /// clones must reload to the root hash of an in-memory advance of + /// the same cycles, and the clone source must not move. + #[test] + fn test_clone_stored_round_trip_sharing_all() -> Result<()> { + const PREFIX: u64 = 5_000_000; + const TARGET: u64 = 15_000_000; + let tmp = tempfile::tempdir().expect("failed creating a temp dir"); + let stored = tmp.path().join("stored"); + let hashed = tmp.path().join("hashed"); + let crashed = tmp.path().join("crashed"); + + let config = make_basic_machine_config(); + let mut machine = create_machine(&config)?; + assert_eq!( + machine.run(PREFIX)?, + constants::break_reason::REACHED_TARGET_MCYCLE + ); + machine.store(&stored)?; + assert_eq!( + machine.run(TARGET)?, + constants::break_reason::REACHED_TARGET_MCYCLE + ); + let expected = machine.root_hash()?; + drop(machine); + + // Advance one clone on disk with the sidecars brought exact + // before the drop. + Machine::clone_stored(&stored, &hashed)?; + { + let mut on_disk = Machine::load_with_sharing( + &hashed, + &RuntimeConfig::quiet_console(), + SharingMode::All, + )?; + on_disk.run(TARGET)?; + on_disk.root_hash()?; + } + + // Advance another and drop it cold: no root_hash, stale hash + // sidecars, dirtiness recorded. The crash shape. + Machine::clone_stored(&stored, &crashed)?; + { + let mut on_disk = Machine::load_with_sharing( + &crashed, + &RuntimeConfig::quiet_console(), + SharingMode::All, + )?; + on_disk.run(TARGET)?; + } + + for dir in [&hashed, &crashed] { + let mut reloaded = Machine::load(dir, &RuntimeConfig::quiet_console())?; + assert_eq!(reloaded.mcycle()?, TARGET); + assert_eq!(reloaded.root_hash()?, expected); + } + + // Clone isolation: writes to the clones never reach the source. + let mut source = Machine::load(&stored, &RuntimeConfig::quiet_console())?; + assert_eq!(source.mcycle()?, PREFIX); + + Ok(()) + } + + /// A SharingMode::All load owns its directory for its lifetime (the + /// emulator's advisory locks): cloning it must fail until the + /// machine drops. And remove_stored deletes a stored machine + /// wholesale. + #[test] + fn test_clone_stored_locked_while_loaded_shared() -> Result<()> { + let tmp = tempfile::tempdir().expect("failed creating a temp dir"); + let stored = tmp.path().join("stored"); + let blocked = tmp.path().join("blocked"); + let after = tmp.path().join("after"); + + let config = make_basic_machine_config(); + let mut machine = create_machine(&config)?; + machine.run(1_000_000)?; + machine.store(&stored)?; + drop(machine); + + { + let _held = Machine::load_with_sharing( + &stored, + &RuntimeConfig::quiet_console(), + SharingMode::All, + )?; + assert!( + Machine::clone_stored(&stored, &blocked).is_err(), + "cloning a directory loaded SharingMode::All must fail" + ); + } + + // A fresh destination after the drop: the failed attempt may + // leave partial files behind, but the lock is gone. + Machine::clone_stored(&stored, &after)?; + assert!(after.join("config.json").exists()); + + Machine::remove_stored(&after)?; + assert!(!after.exists()); + + Ok(()) + } } diff --git a/machine/rust-bindings/cartesi-machine/src/types/mod.rs b/machine/rust-bindings/cartesi-machine/src/types/mod.rs index b1841f3e7..1cf2acfd9 100644 --- a/machine/rust-bindings/cartesi-machine/src/types/mod.rs +++ b/machine/rust-bindings/cartesi-machine/src/types/mod.rs @@ -11,6 +11,33 @@ pub type Register = cartesi_machine_sys::cm_reg; pub type BreakReason = cartesi_machine_sys::cm_break_reason; pub type UArchBreakReason = cartesi_machine_sys::cm_uarch_break_reason; +/// Backing-store sharing mode (`cm_sharing_mode`): where a loaded +/// machine's mutations live. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SharingMode { + /// Fully in-memory: files are mapped privately, mutations are + /// discarded on destroy. + None, + /// Per-range `shared` flags from the stored config (the emulator + /// default; typically everything private). + Config, + /// Fully on-disk: every file is mapped shared, mutations land in + /// the loaded directory as they happen. The machine holds an + /// exclusive advisory lock on the directory's writable files for + /// its lifetime. + All, +} + +impl From for cartesi_machine_sys::cm_sharing_mode { + fn from(mode: SharingMode) -> Self { + match mode { + SharingMode::None => cartesi_machine_sys::CM_SHARING_NONE, + SharingMode::Config => cartesi_machine_sys::CM_SHARING_CONFIG, + SharingMode::All => cartesi_machine_sys::CM_SHARING_ALL, + } + } +} + #[derive(Clone, Debug, Default)] pub struct LogType { pub annotations: bool, diff --git a/prt/client-rs/core/Cargo.toml b/prt/client-rs/core/Cargo.toml deleted file mode 100644 index 9ef391375..000000000 --- a/prt/client-rs/core/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "cartesi-prt-core" -description = "A Cartesi validator reference implementation" - -version = { workspace = true} -authors = { workspace = true} -edition = { workspace = true} -homepage = { workspace = true} -license-file = { workspace = true} -readme = { workspace = true} -repository = { workspace = true} - -[dependencies] -# common-rs -cartesi-dave-arithmetic = { workspace = true } -cartesi-dave-merkle = { workspace = true } -cartesi-dave-kms = { workspace = true } - -# machine bindings -cartesi-machine = { workspace = true } - -# solidity bindings -cartesi-prt-contracts = { workspace = true } - -alloy = { workspace = true, features = ["sol-types", "contract", "network", "reqwest", "signers", "signer-local"] } -ruint = { workspace = true, features = ["num-traits"] } - -# async -async-recursion = { workspace = true } -async-trait = { workspace = true } -tokio = { workspace = true, features = ["full"] } - -anyhow = { workspace = true } -thiserror = { workspace = true } - - -clap = { workspace = true, features = ["derive", "env"] } -hex = { workspace = true } -log = { workspace = true } -num-traits = { workspace = true } - -lazy_static = { workspace = true } -rusqlite = { workspace = true } -rusqlite_migration = { workspace = true } - -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -[dev-dependencies] -tempfile = "3" diff --git a/prt/client-rs/core/src/db/dispute_state_access.rs b/prt/client-rs/core/src/db/dispute_state_access.rs deleted file mode 100644 index 07997feac..000000000 --- a/prt/client-rs/core/src/db/dispute_state_access.rs +++ /dev/null @@ -1,411 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use crate::{ - db::sql::{dispute_data, error::*, migrations}, - machine::constants, -}; -use cartesi_dave_arithmetic::max_uint; -use cartesi_dave_merkle::{Digest, MerkleBuilder, MerkleTree}; - -use alloy::{hex as alloy_hex, primitives::U256}; -use log::info; -use rusqlite::{Connection, OpenFlags}; -use serde::{Deserialize, Serialize}; -use std::{ - fs, - path::{Path, PathBuf}, - sync::{Arc, Mutex}, -}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct InputsAndLeafs { - inputs: Vec, - leafs: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct Input(#[serde(with = "alloy_hex::serde")] pub Vec); - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Leaf { - #[serde(with = "alloy_hex::serde")] - pub hash: [u8; 32], - pub repetitions: u64, -} - -#[derive(Debug)] -pub struct DisputeStateAccess { - connection: Mutex, - pub work_path: PathBuf, -} - -use std::fs::File; -use std::io::Read; - -fn read_json_file(file_path: &Path) -> Result { - let mut file = File::open(file_path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let data: InputsAndLeafs = serde_json::from_str(&contents)?; - Ok(data) -} - -impl DisputeStateAccess { - pub fn new( - inputs: Vec, - leafs: Vec, - _root_tournament: String, - compute_data_path: PathBuf, - ) -> Result { - // initialize the database if it doesn't exist - // fill the database from a json-format file, or the parameters - // the database should be "./db" - // the json file should be "/compute_data/0x_root_tournament_address/inputs_and_leafs.json" - let work_path = compute_data_path; - if !work_path.exists() { - fs::create_dir_all(&work_path)?; - } - let db_path = work_path.join("db"); - let no_create_flags = OpenFlags::default() & !OpenFlags::SQLITE_OPEN_CREATE; - match Connection::open_with_flags(&db_path, no_create_flags) { - // database already exists, return it - Ok(connection) => { - connection - .busy_timeout(std::time::Duration::from_secs(10)) - .map_err(anyhow::Error::from) - .unwrap(); - Ok(Self { - connection: Mutex::new(connection), - work_path, - }) - } - Err(_) => { - info!("create new database for dispute"); - let mut connection = Connection::open(&db_path)?; - migrations::migrate_to_latest(&mut connection).unwrap(); - connection - .busy_timeout(std::time::Duration::from_secs(10)) - .map_err(anyhow::Error::from) - .unwrap(); - - connection - .query_row("PRAGMA journal_mode=WAL;", [], |_| Ok(())) - .map_err(anyhow::Error::from) - .unwrap(); - - let json_path = work_path.join("inputs_and_leafs.json"); - // prioritize json file over parameters - match read_json_file(&json_path) { - Ok(inputs_and_leafs) => { - info!("load inputs and leafs from json file"); - dispute_data::insert_compute_data( - &connection, - inputs_and_leafs.inputs.iter(), - inputs_and_leafs.leafs.iter(), - )?; - } - Err(_) => { - info!("load inputs and leafs from parameters"); - dispute_data::insert_compute_data( - &connection, - inputs.iter(), - leafs.iter(), - )?; - } - } - - Ok(Self { - connection: Mutex::new(connection), - work_path, - }) - } - } - } - - pub fn input(&self, id: u64) -> Result>> { - let conn = self.connection.lock().unwrap(); - dispute_data::input(&conn, id) - } - - pub fn inputs(&self) -> Result>> { - let conn = self.connection.lock().unwrap(); - dispute_data::inputs(&conn) - } - - pub fn insert_leafs<'a>( - &self, - level: u64, - base_cycle: U256, - leafs: impl Iterator, - ) -> Result<()> { - let conn = self.connection.lock().unwrap(); - dispute_data::insert_leafs(&conn, level, base_cycle, leafs) - } - - pub fn leafs( - &self, - level: u64, - log2_stride: u64, - log2_stride_count: u64, - base_cycle: U256, - ) -> Result, u64)>> { - let conn = self.connection.lock().unwrap(); - let leafs: Vec = dispute_data::leafs(&conn, level, base_cycle)? - .iter() - .map(|(leaf, repetitions)| Leaf { - hash: <[u8; 32]>::try_from(leaf.as_slice()) - .expect("leaf slice with incorrect length"), - repetitions: *repetitions, - }) - .collect(); - - let mut tree = Vec::new(); - if log2_stride == 0 && !leafs.is_empty() { - tree = self.leafs_with_uarch(leafs, log2_stride_count)?; - } else { - for leaf in leafs { - tree.push((Digest::from_digest(&leaf.hash)?.into(), leaf.repetitions)); - } - } - - Ok(tree) - } - - fn leafs_with_uarch( - &self, - leafs: Vec, - log2_stride_count: u64, - ) -> Result, u64)>> { - let mut main_tree = Vec::new(); - let span_count = max_uint(log2_stride_count - constants::LOG2_UARCH_SPAN_TO_BARCH) + 1; - let span_size = constants::UARCH_SPAN_TO_BARCH + 1; - let mut accumulated_repetitions = 0; - let mut uarch_tree_builder = MerkleBuilder::default(); - - for leaf in leafs { - if accumulated_repetitions == 0 { - // reset the uarch_tree builder - uarch_tree_builder = MerkleBuilder::default(); - } - - if accumulated_repetitions < span_size { - uarch_tree_builder - .append_repeated(Digest::from_digest(&leaf.hash)?, leaf.repetitions); - accumulated_repetitions += leaf.repetitions; - } - if accumulated_repetitions == span_size { - // here we build a uarch_tree and add it to the main tree - main_tree.push((uarch_tree_builder.build(), 1)); - // reset the accumulated repetitions - accumulated_repetitions = 0; - } - } - - assert!(main_tree.len() > 0); - let main_tree_len = main_tree.len() as u64; - if main_tree_len < span_count { - main_tree.push((uarch_tree_builder.build(), span_count - main_tree_len)); - } - - Ok(main_tree) - } - - /* - pub fn closest_snapshot(&self, base_cycle: u64) -> Result> { - let mut snapshots = Vec::new(); - - // iterate through the snapshot directory, find the one whose cycle number is closest to the base_cycle - for entry in fs::read_dir(&self.work_path)? { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if name.chars().all(char::is_numeric) { - if let Ok(number) = name.parse::() { - snapshots.push((number, path)); - } - } - } - } - } - - snapshots.sort_by_key(|k| k.0); - let pos = snapshots - .binary_search_by_key(&base_cycle, |k| k.0) - .unwrap_or_else(|x| if x > 0 { x - 1 } else { x }); - - let snapshot = { - match snapshots.get(pos) { - Some(t) => { - if t.0 > base_cycle { - None - } else { - Some(t.clone()) - } - } - // snapshots.get(pos).map(|t| t.clone()), - None => None, - } - }; - - Ok(snapshot) - } - */ -} - -#[cfg(test)] -mod compute_state_access_tests { - use super::*; - - #[test] - fn test_access_sequentially() { - // test_closest_snapshot(); - // test_none_match(); - } - - /* - fn test_closest_snapshot() { - let work_dir = PathBuf::from("/tmp/12345678"); - remove_directory(&work_dir).unwrap(); - create_directory(&work_dir).unwrap(); - { - let access = DisputeStateAccess::new( - None, - Vec::new(), - String::from("12345678"), - PathBuf::from("/tmp"), - ) - .unwrap(); - - assert_eq!(access.closest_snapshot(0).unwrap(), None); - assert_eq!(access.closest_snapshot(100).unwrap(), None); - assert_eq!(access.closest_snapshot(150).unwrap(), None); - assert_eq!(access.closest_snapshot(200).unwrap(), None); - assert_eq!(access.closest_snapshot(300).unwrap(), None); - assert_eq!(access.closest_snapshot(9000).unwrap(), None); - assert_eq!(access.closest_snapshot(9999).unwrap(), None); - - for cycle in [99999, 0, 1, 5, 99, 300, 150, 200] { - create_directory(&access.work_path.join(format!("{cycle}"))).unwrap(); - } - - assert_eq!( - access.closest_snapshot(100).unwrap(), - Some((99, access.work_path.join("99"))) - ); - - assert_eq!( - access.closest_snapshot(150).unwrap(), - Some((150, access.work_path.join("150"))) - ); - - assert_eq!( - access.closest_snapshot(200).unwrap(), - Some((200, access.work_path.join("200"))) - ); - - assert_eq!( - access.closest_snapshot(300).unwrap(), - Some((300, access.work_path.join("300"))) - ); - - assert_eq!( - access.closest_snapshot(7).unwrap(), - Some((5, access.work_path.join("5"))) - ); - - assert_eq!( - access.closest_snapshot(10000).unwrap(), - Some((300, access.work_path.join("300"))) - ); - - assert_eq!( - access.closest_snapshot(100000).unwrap(), - Some((99999, access.work_path.join("99999"))) - ); - } - - remove_directory(&work_dir).unwrap(); - } - - fn test_none_match() { - let work_dir = PathBuf::from("/tmp/12345678"); - remove_directory(&work_dir).unwrap(); - create_directory(&work_dir).unwrap(); - { - let access = DisputeStateAccess::new( - None, - Vec::new(), - String::from("12345678"), - PathBuf::from("/tmp"), - ) - .unwrap(); - - let cycle: u64 = 844424930131968; - { - let c = cycle; - create_directory(&access.work_path.join(format!("{c}"))).unwrap(); - } - - assert_eq!(access.closest_snapshot(0).unwrap(), None); - assert_eq!(access.closest_snapshot(5629).unwrap(), None); - assert_eq!(access.closest_snapshot(5629499).unwrap(), None); - assert_eq!(access.closest_snapshot(56294995342).unwrap(), None); - assert_eq!(access.closest_snapshot(562949953421312).unwrap(), None); - assert_eq!( - access.closest_snapshot(cycle).unwrap(), - Some((cycle, access.work_path.join(format!("{}", cycle)))) - ); - assert_eq!( - access.closest_snapshot(cycle + 1).unwrap(), - Some((cycle, access.work_path.join(format!("{}", cycle)))) - ); - - remove_directory(&work_dir).unwrap(); - } - } - */ - - #[test] - fn test_deserialize() { - let json_str_1 = r#"{"inputs": [], "leafs": [ - {"hash":"0x01020304050607abcdef01020304050607abcdef01020304050607abcdef0102", "repetitions":20}, - {"hash":"0x01020304050607fedcba01020304050607fedcba01020304050607fedcba0102", "repetitions":13}]}"#; - let inputs_and_leafs_1: InputsAndLeafs = serde_json::from_str(json_str_1).unwrap(); - assert_eq!(inputs_and_leafs_1.inputs.len(), 0); - assert_eq!(inputs_and_leafs_1.leafs.len(), 2); - assert_eq!( - inputs_and_leafs_1.leafs[0].hash, - [ - 1, 2, 3, 4, 5, 6, 7, 171, 205, 239, 1, 2, 3, 4, 5, 6, 7, 171, 205, 239, 1, 2, 3, 4, - 5, 6, 7, 171, 205, 239, 1, 2 - ] - ); - assert_eq!( - inputs_and_leafs_1.leafs[1].hash, - [ - 1, 2, 3, 4, 5, 6, 7, 254, 220, 186, 1, 2, 3, 4, 5, 6, 7, 254, 220, 186, 1, 2, 3, 4, - 5, 6, 7, 254, 220, 186, 1, 2 - ] - ); - - let json_str_2 = r#"{"inputs": [], "leafs": [ - {"hash":"0x01020304050607abcdef01020304050607abcdef01020304050607abcdef0102", "repetitions": 20}, - {"hash":"0x01020304050607fedcba01020304050607fedcba01020304050607fedcba0102", "repetitions": 13}]}"#; - let inputs_and_leafs_2: InputsAndLeafs = serde_json::from_str(json_str_2).unwrap(); - assert_eq!(inputs_and_leafs_2.inputs.len(), 0); - assert_eq!(inputs_and_leafs_2.leafs.len(), 2); - - let json_str_3 = r#"{"inputs": ["0x12345678", "0x22345678"], "leafs": [ - {"hash":"0x01020304050607abcdef01020304050607abcdef01020304050607abcdef0102", "repetitions": 20}, - {"hash":"0x01020304050607fedcba01020304050607fedcba01020304050607fedcba0102", "repetitions": 13}]}"#; - let inputs_and_leafs_3: InputsAndLeafs = serde_json::from_str(json_str_3).unwrap(); - let inputs_3 = inputs_and_leafs_3.inputs; - assert_eq!(inputs_3.len(), 2); - assert_eq!(inputs_and_leafs_3.leafs.len(), 2); - assert_eq!(inputs_3[0].0, [18, 52, 86, 120]); - assert_eq!(inputs_3[1].0, [34, 52, 86, 120]); - } -} diff --git a/prt/client-rs/core/src/db/mod.rs b/prt/client-rs/core/src/db/mod.rs deleted file mode 100644 index 050effad3..000000000 --- a/prt/client-rs/core/src/db/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pub mod dispute_state_access; - -pub(crate) mod sql; diff --git a/prt/client-rs/core/src/db/sql/dispute_data.rs b/prt/client-rs/core/src/db/sql/dispute_data.rs deleted file mode 100644 index b7e9e11e3..000000000 --- a/prt/client-rs/core/src/db/sql/dispute_data.rs +++ /dev/null @@ -1,305 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use super::error::*; -use crate::db::dispute_state_access::{Input, Leaf}; - -use alloy::primitives::U256; -use rusqlite::{OptionalExtension, params}; - -// -// Inputs -// - -pub fn insert_inputs<'a>( - conn: &rusqlite::Connection, - inputs: impl Iterator, -) -> Result<()> { - let mut stmt = insert_input_statement(conn)?; - for (i, input) in inputs.enumerate() { - if stmt.execute(params![i, input.0])? != 1 { - return Err(DisputeStateAccessError::InsertionFailed { - description: "input insertion failed".to_owned(), - }); - } - } - - Ok(()) -} - -fn insert_input_statement(conn: &rusqlite::Connection) -> Result> { - Ok(conn.prepare( - "\ - INSERT INTO inputs (input_index, input) VALUES (?1, ?2) - ", - )?) -} - -pub fn input(conn: &rusqlite::Connection, id: u64) -> Result>> { - let mut stmt = conn.prepare( - "\ - SELECT * FROM inputs - WHERE input_index = ?1 - ", - )?; - - let i = stmt - .query_row(params![id], |row| row.get("input")) - .optional()?; - - Ok(i) -} - -pub fn inputs(conn: &rusqlite::Connection) -> Result>> { - let mut stmt = conn.prepare( - "\ - SELECT * FROM inputs - ORDER BY input_index ASC - ", - )?; - - let query = stmt.query_map([], |r| r.get("input"))?; - - let mut res = vec![]; - for row in query { - res.push(row?); - } - - Ok(res) -} - -// -// Compute leafs -// - -pub fn insert_leafs<'a>( - conn: &rusqlite::Connection, - level: u64, - base_cycle: U256, - leafs: impl Iterator, -) -> Result<()> { - let leafs_count = leafs_count(conn, level, base_cycle)?; - let mut stmt = insert_leaf_statement(conn)?; - for (i, leaf) in leafs.enumerate() { - assert!(leaf.repetitions > 0); - if stmt.execute(params![ - level, - base_cycle.as_le_slice(), - i + leafs_count, - leaf.hash, - leaf.repetitions - ])? != 1 - { - return Err(DisputeStateAccessError::InsertionFailed { - description: "compute leafs insertion failed".to_owned(), - }); - } - } - - Ok(()) -} - -fn insert_leaf_statement(conn: &rusqlite::Connection) -> Result> { - Ok(conn.prepare( - "\ - INSERT INTO leafs (level, base_cycle, leaf_index, leaf, repetitions) VALUES (?1, ?2, ?3, ?4, ?5) - ", - )?) -} - -pub fn leafs( - conn: &rusqlite::Connection, - level: u64, - base_cycle: U256, -) -> Result, u64)>> { - let mut stmt = conn.prepare( - "\ - SELECT * FROM leafs - WHERE level = ?1 AND base_cycle = ?2 - ORDER BY leaf_index ASC - ", - )?; - - let query = stmt.query_map(params![level, base_cycle.as_le_slice()], |r| { - Ok((r.get("leaf")?, r.get("repetitions")?)) - })?; - - let mut res = vec![]; - for row in query { - res.push(row?); - } - - Ok(res) -} - -pub fn leafs_count(conn: &rusqlite::Connection, level: u64, base_cycle: U256) -> Result { - Ok(conn.query_row( - "\ - SELECT count(*) FROM leafs - WHERE level = ?1 AND base_cycle = ?2 - ", - params![level, base_cycle.as_le_slice()], - |row| row.get(0).map(|i: u64| i as usize), - )?) -} - -pub fn insert_compute_data<'a>( - conn: &rusqlite::Connection, - inputs: impl Iterator, - leafs: impl Iterator, -) -> Result<()> { - let tx = conn.unchecked_transaction()?; - insert_inputs(&tx, inputs)?; - insert_leafs(&tx, 0, U256::ZERO, leafs)?; - tx.commit()?; - - Ok(()) -} - -// -// Tests -// - -#[cfg(test)] -mod test_helper { - use crate::db::sql::migrations; - use rusqlite::Connection; - - pub fn setup_db() -> Connection { - let mut conn = Connection::open_in_memory().unwrap(); - migrations::MIGRATIONS.to_latest(&mut conn).unwrap(); - conn - } -} - -#[cfg(test)] -mod inputs_tests { - use super::*; - - #[test] - fn test_empty() { - let conn = test_helper::setup_db(); - assert!(matches!(input(&conn, 0), Ok(None))); - } - - #[test] - fn test_insert() { - let conn = test_helper::setup_db(); - let data = vec![1]; - - assert!(matches!( - insert_inputs(&conn, [Input(data.clone()), Input(data.clone())].iter(),), - Ok(()) - )); - - assert!(matches!(input(&conn, 0), Ok(Some(_)))); - assert!(matches!(input(&conn, 1), Ok(Some(_)))); - - // overwrite inputs is forbidden - assert!(insert_inputs(&conn, [Input(data.clone()), Input(data.clone())].iter()).is_err()); - } -} - -#[cfg(test)] -mod leafs_tests { - use super::*; - - #[test] - fn test_empty() { - let conn = test_helper::setup_db(); - assert!(matches!(leafs(&conn, 0, U256::from(0)).unwrap().len(), 0)); - assert!(matches!(leafs(&conn, 0, U256::from(1)).unwrap().len(), 0)); - assert!(matches!(leafs(&conn, 1, U256::from(1)).unwrap().len(), 0)); - } - - #[test] - fn test_insert() { - let conn = test_helper::setup_db(); - let data = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, - ]; - - assert!(matches!( - insert_leafs( - &conn, - 0, - U256::from(0), - [ - Leaf { - hash: data, - repetitions: 1 - }, - Leaf { - hash: data, - repetitions: 2 - }, - ] - .iter(), - ), - Ok(()) - )); - assert!(matches!(leafs(&conn, 0, U256::from(0)).unwrap().len(), 2)); - // compute leafs can be accumulated - assert!(matches!( - insert_leafs( - &conn, - 0, - U256::from(0), - [ - Leaf { - hash: data, - repetitions: 1 - }, - Leaf { - hash: data, - repetitions: 2 - }, - ] - .iter(), - ), - Ok(()) - )); - assert!(matches!(leafs(&conn, 0, U256::from(0)).unwrap().len(), 4)); - assert!(matches!( - insert_leafs( - &conn, - 0, - U256::from(1), - [ - Leaf { - hash: data, - repetitions: 1 - }, - Leaf { - hash: data, - repetitions: 2 - }, - ] - .iter(), - ), - Ok(()) - )); - assert!(matches!(leafs(&conn, 0, U256::from(1)).unwrap().len(), 2)); - assert!(matches!( - insert_leafs( - &conn, - 1, - U256::from(0), - [ - Leaf { - hash: data, - repetitions: 1 - }, - Leaf { - hash: data, - repetitions: 2 - }, - ] - .iter(), - ), - Ok(()) - )); - assert!(matches!(leafs(&conn, 1, U256::from(0)).unwrap().len(), 2)); - } -} diff --git a/prt/client-rs/core/src/db/sql/error.rs b/prt/client-rs/core/src/db/sql/error.rs deleted file mode 100644 index c257abb14..000000000 --- a/prt/client-rs/core/src/db/sql/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum DisputeStateAccessError { - #[error(transparent)] - Digest { - #[from] - source: cartesi_dave_merkle::DigestError, - }, - - #[error(transparent)] - IO { - #[from] - source: std::io::Error, - }, - - #[error(transparent)] - Serde { - #[from] - source: serde_json::Error, - }, - - #[error(transparent)] - SQLite { - #[from] - source: rusqlite::Error, - }, - - #[error("Failed to insert data: `{description}`")] - InsertionFailed { description: String }, -} - -pub type Result = std::result::Result; diff --git a/prt/client-rs/core/src/db/sql/migrations.rs b/prt/client-rs/core/src/db/sql/migrations.rs deleted file mode 100644 index 65a8c0087..000000000 --- a/prt/client-rs/core/src/db/sql/migrations.rs +++ /dev/null @@ -1,12 +0,0 @@ -use lazy_static::lazy_static; -use rusqlite::Connection; -use rusqlite_migration::{M, Migrations}; - -lazy_static! { - pub static ref MIGRATIONS: Migrations<'static> = - Migrations::new(vec![M::up(include_str!("migrations.sql")),]); -} - -pub fn migrate_to_latest(conn: &mut Connection) -> Result<(), rusqlite_migration::Error> { - MIGRATIONS.to_latest(conn) -} diff --git a/prt/client-rs/core/src/db/sql/migrations.sql b/prt/client-rs/core/src/db/sql/migrations.sql deleted file mode 100644 index 08dffbafb..000000000 --- a/prt/client-rs/core/src/db/sql/migrations.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE inputs ( - input_index INTEGER NOT NULL PRIMARY KEY, - input BLOB NOT NULL -); - -CREATE TABLE leafs ( - level INTEGER NOT NULL, - base_cycle BLOB NOT NULL, - leaf_index INTEGER NOT NULL, - repetitions INTEGER NOT NULL, - leaf BLOB NOT NULL, - PRIMARY KEY (level, base_cycle, leaf_index) -); diff --git a/prt/client-rs/core/src/db/sql/mod.rs b/prt/client-rs/core/src/db/sql/mod.rs deleted file mode 100644 index b072fed3c..000000000 --- a/prt/client-rs/core/src/db/sql/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod dispute_data; -pub mod error; -pub mod migrations; diff --git a/prt/client-rs/core/src/lib.rs b/prt/client-rs/core/src/lib.rs deleted file mode 100644 index 7894fd84b..000000000 --- a/prt/client-rs/core/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! `cartesi-prt-core` is a crate that defines a lot of data structures for the creation of Merkle -//! trees and tournaments using the Cartesi Machine. - -pub mod db; -pub mod machine; -pub mod strategy; -pub mod tournament; diff --git a/prt/client-rs/core/src/machine/commitment_builder.rs b/prt/client-rs/core/src/machine/commitment_builder.rs deleted file mode 100644 index 449c860ab..000000000 --- a/prt/client-rs/core/src/machine/commitment_builder.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! The builder of machine commitments [MachineCommitmentBuilder] is responsible for building the -//! [MachineCommitment]. It is used by the [Arena] to build the commitments of the tournaments. - -use crate::{ - db::dispute_state_access::DisputeStateAccess, - machine::{ - MachineCommitment, MachineInstance, build_machine_commitment, - build_machine_commitment_from_leafs, error::Result, - }, -}; - -use alloy::primitives::U256; -use log::trace; - -pub struct MachineCommitmentBuilder { - machine_path: String, -} - -impl MachineCommitmentBuilder { - pub fn new(machine_path: String) -> Self { - MachineCommitmentBuilder { machine_path } - } - - pub fn build_commitment( - &mut self, - base_cycle: U256, - level: u64, - log2_stride: u64, - log2_stride_count: u64, - db: &DisputeStateAccess, - ) -> Result { - let mut machine = - MachineInstance::new_rollups_advanced_until(&self.machine_path, base_cycle, db)?; - let initial_state = machine.root_hash()?; - - trace!("initial state for commitment: {}", initial_state); - let commitment = { - let mut leafs = db.leafs(level, log2_stride, log2_stride_count, base_cycle)?; - // leafs are cached in database, use it to calculate merkle - if leafs.is_empty() { - // leafs are not cached, build merkle by running the machine - leafs = build_machine_commitment( - &mut machine, - base_cycle, - level, - log2_stride, - log2_stride_count, - db, - )?; - assert!(!leafs.is_empty()); - } - build_machine_commitment_from_leafs(leafs, initial_state)? - }; - - Ok(commitment) - } -} diff --git a/prt/client-rs/core/src/machine/mod.rs b/prt/client-rs/core/src/machine/mod.rs deleted file mode 100644 index de4631a95..000000000 --- a/prt/client-rs/core/src/machine/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Module for communication with the Cartesi machine using RPC and construction of computation -//! hashes. - -#[doc(hidden)] -pub mod constants; - -pub mod instance; -pub use instance::*; - -mod commitment; -pub use commitment::*; - -mod commitment_builder; -pub use commitment_builder::*; - -pub mod error; diff --git a/prt/client-rs/core/src/strategy/gc.rs b/prt/client-rs/core/src/strategy/gc.rs deleted file mode 100644 index 1e4ddce74..000000000 --- a/prt/client-rs/core/src/strategy/gc.rs +++ /dev/null @@ -1,102 +0,0 @@ -use ::log::debug; -use alloy::primitives::Address; -use async_recursion::async_recursion; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::strategy::error::Result; -use crate::tournament::{ArenaSender, MatchState, TournamentStateMap}; - -pub struct GarbageCollector { - arena_sender: Arc>, - root_tournamet: Address, -} - -impl GarbageCollector { - pub fn new(arena_sender: Arc>, root_tournamet: Address) -> Self { - Self { - arena_sender, - root_tournamet, - } - } - - pub async fn react(&self, tournament_states: &TournamentStateMap) -> Result<()> { - self.react_tournament(self.root_tournamet, tournament_states) - .await - } - - #[async_recursion] - async fn react_tournament<'a>( - &self, - tournament_address: Address, - tournament_states: &TournamentStateMap, - ) -> Result<()> { - let tournament_state = tournament_states - .get(&tournament_address) - .expect("tournament state not found"); - - for m in tournament_state.matches.iter() { - self.react_match(m, tournament_states, tournament_address) - .await?; - - let status_1 = tournament_state - .commitment_states - .get(&m.id.commitment_one) - .expect("status of commitment 1 not found"); - let status_2 = tournament_state - .commitment_states - .get(&m.id.commitment_two) - .expect("status of commitment 2 not found"); - if (!status_1.clock.has_time() - && (status_1.clock.time_since_timeout() > status_2.clock.allowance)) - || (!status_2.clock.has_time() - && (status_2.clock.time_since_timeout() > status_1.clock.allowance)) - { - debug!( - "eliminate match for commitment {} and {} at tournament {} of level {}", - m.id.commitment_one, - m.id.commitment_two, - tournament_address, - tournament_state.level - ); - - self.arena_sender - .lock() - .await - .eliminate_match(tournament_address, m.id) - .await?; - } - } - Ok(()) - } - - #[async_recursion] - async fn react_match<'a>( - &self, - match_state: &MatchState, - tournament_states: &TournamentStateMap, - tournament_address: Address, - ) -> Result<()> { - if let Some(inner_tournament_address) = match_state.inner_tournament { - let inner_tournament_state = tournament_states - .get(&inner_tournament_address) - .expect("tournament state not found"); - - if inner_tournament_state.can_be_eliminated { - debug!( - "eliminate inner tournament {inner_tournament_address} of level {}, child of tournament {tournament_address}", - inner_tournament_state.level - ); - self.arena_sender - .lock() - .await - .eliminate_inner_tournament(tournament_address, inner_tournament_address) - .await?; - } else { - self.react_tournament(inner_tournament_address, tournament_states) - .await?; - } - } - Ok(()) - } -} diff --git a/prt/client-rs/core/src/strategy/mod.rs b/prt/client-rs/core/src/strategy/mod.rs deleted file mode 100644 index e3a9623a7..000000000 --- a/prt/client-rs/core/src/strategy/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! This module defines the struct [Player] that is responsible for reacting to the states -//! of tournaments; and the struct [GarbageCollector] that is responsible for collecting finished matches - -pub mod error; -pub mod gc; -pub mod player; diff --git a/prt/client-rs/core/src/strategy/player.rs b/prt/client-rs/core/src/strategy/player.rs deleted file mode 100644 index 868d83728..000000000 --- a/prt/client-rs/core/src/strategy/player.rs +++ /dev/null @@ -1,490 +0,0 @@ -use std::collections::HashMap; -use std::{path::PathBuf, sync::Arc}; -use tokio::sync::Mutex; - -use crate::strategy::error::Result; -use ::log::{debug, error, info}; -use alloy::{primitives::Address, providers::DynProvider}; -use async_recursion::async_recursion; -use num_traits::One; -use ruint::aliases::U256; - -use crate::{ - db::dispute_state_access::{DisputeStateAccess, Input, Leaf}, - machine::{MachineCommitment, MachineCommitmentBuilder, MachineInstance}, - strategy::gc::GarbageCollector, - tournament::{ - ArenaSender, CommitmentState, MatchState, StateReader, TournamentState, TournamentStateMap, - TournamentWinner, - }, -}; -use cartesi_dave_merkle::{Digest, MerkleProof}; - -#[derive(Debug, PartialEq)] -pub enum PlayerTournamentResult { - TournamentLost, - TournamentRunning, - TournamentWon, -} - -pub struct Player { - arena_sender: Arc>, - db: DisputeStateAccess, - machine_path: String, - commitment_builder: MachineCommitmentBuilder, - root_tournament: Address, - reader: StateReader, - gc: GarbageCollector, -} - -impl Player { - pub fn new( - arena_sender: Arc>, - inputs: Vec, - leafs: Vec, - provider: DynProvider, - machine_path: String, - root_tournament: Address, - block_created_number: u64, - long_block_range_error_codes: Vec, - state_dir: PathBuf, - ) -> Result { - let db = DisputeStateAccess::new(inputs, leafs, root_tournament.to_string(), state_dir)?; - let reader = StateReader::new( - provider.clone(), - block_created_number, - long_block_range_error_codes, - )?; - let gc = GarbageCollector::new(arena_sender.clone(), root_tournament); - let commitment_builder = MachineCommitmentBuilder::new(machine_path.clone()); - Ok(Self { - arena_sender, - db, - machine_path, - commitment_builder, - root_tournament, - reader, - gc, - }) - } - - pub async fn react(&mut self) -> Result { - let tournament_states = self.reader.fetch_from_root(self.root_tournament).await?; - - self.gc.react(&tournament_states).await?; - self.react_tournament(None, self.root_tournament, &tournament_states) - .await - } - - #[async_recursion] - async fn react_tournament<'a>( - &mut self, - old_commitment: Option<&MachineCommitment>, - tournament_address: Address, - tournament_states: &TournamentStateMap, - ) -> Result { - info!("Enter tournament at address: {}", tournament_address); - // TODO: print final state one and final state two - let tournament_state = get_tournament_state(tournament_states, tournament_address); - - let commitment = self.commitment_builder.build_commitment( - tournament_state.base_cycle, - tournament_state.level, - tournament_state.log2_stride, - tournament_state.log2_stride_count, - &self.db, - )?; - - if let Some(winner) = &tournament_state.winner { - match winner { - TournamentWinner::Root(winner_commitment, winner_state) => { - info!( - "tournament finished, winner commitment: {}, state hash: {}", - winner_commitment, winner_state, - ); - if commitment.merkle.root_hash() == *winner_commitment { - info!("player won tournament {}", tournament_state.address); - return Ok(PlayerTournamentResult::TournamentWon); - } else { - error!("player lost tournament {}", tournament_state.address); - return Ok(PlayerTournamentResult::TournamentLost); - } - } - TournamentWinner::Inner(parent_commitment, _) => { - match old_commitment { - Some(old_commitment) => { - if *parent_commitment != old_commitment.merkle.root_hash() { - error!("player lost tournament {}", tournament_state.address); - return Ok(PlayerTournamentResult::TournamentLost); - } else { - info!( - "win tournament {} of level {} for commitment {}", - tournament_state.address, - tournament_state.level, - commitment.merkle.root_hash(), - ); - let (left, right) = old_commitment - .merkle - .subtrees() - .expect("merkle tree should have subtrees"); - self.arena_sender - .lock() - .await - .win_inner_match( - tournament_state - .parent - .expect("parent tournament state not found"), - tournament_state.address, - left.root_hash(), - right.root_hash(), - ) - .await?; - - return Ok(PlayerTournamentResult::TournamentRunning); - } - } - None => { - panic!("parent tournament state not found for inner tournament"); - } - }; - } - } - } - - let commitment_state = tournament_state - .commitment_states - .get(&commitment.merkle.root_hash()); - match commitment_state { - Some(c) => { - info!("{}", c.clock); - if let Some(m) = c.latest_match { - let match_state = tournament_state - .matches - .get(m) - .expect("match state not found"); - - self.react_match(match_state, commitment, tournament_state, tournament_states) - .await?; - } else { - info!( - "no match found for commitment: {}", - commitment.merkle.root_hash() - ); - } - } - None => { - self.join_tournament_if_needed(tournament_state, &commitment) - .await?; - } - } - - Ok(PlayerTournamentResult::TournamentRunning) - } - - async fn join_tournament_if_needed( - &mut self, - tournament_state: &TournamentState, - commitment: &MachineCommitment, - ) -> Result<()> { - let (left, right) = commitment - .merkle - .subtrees() - .expect("commitment should have subtrees"); - let proof_last = commitment.merkle.prove_last(); - - info!( - "join tournament {} of level {} with commitment {}", - tournament_state.address, - tournament_state.level, - commitment.merkle.root_hash(), - ); - - // Get the bond value required for joining the tournament - let bond_value = self - .arena_sender - .lock() - .await - .bond_value(tournament_state.address) - .await?; - - self.arena_sender - .lock() - .await - .join_tournament( - tournament_state.address, - &proof_last, - left.root_hash(), - right.root_hash(), - bond_value, - ) - .await?; - - Ok(()) - } - - #[async_recursion] - async fn react_match<'a>( - &mut self, - match_state: &MatchState, - commitment: MachineCommitment, - tournament_state: &TournamentState, - tournament_states: &TournamentStateMap, - ) -> Result<()> { - info!("Enter match at HEIGHT: {}", match_state.current_height); - - let commitment_states = &tournament_state.commitment_states; - - self.win_timeout_match( - match_state, - &commitment, - commitment_states, - tournament_state.level, - ) - .await?; - - if match_state.current_height == 0 { - self.react_sealed_match( - match_state, - &commitment, - tournament_state.level, - tournament_state.max_level, - tournament_states, - ) - .await?; - } else if match_state.current_height == 1 { - self.react_unsealed_match( - match_state, - &commitment, - tournament_state.level, - tournament_state.max_level, - ) - .await?; - } else { - self.react_running_match(match_state, &commitment, tournament_state.level) - .await?; - } - Ok(()) - } - - async fn win_timeout_match( - &mut self, - match_state: &MatchState, - commitment: &MachineCommitment, - commitment_states: &HashMap, - tournament_level: u64, - ) -> Result<()> { - let opponent_clock = if commitment.merkle.root_hash() == match_state.id.commitment_one { - commitment_states - .get(&match_state.id.commitment_two) - .unwrap() - .clock - } else { - commitment_states - .get(&match_state.id.commitment_one) - .unwrap() - .clock - }; - - if !opponent_clock.has_time() { - let (left, right) = commitment - .merkle - .subtrees() - .expect("merkle tree should have subtrees"); - - info!( - "win match by timeout in tournament {} of level {} for commitment {}", - match_state.tournament_address, - tournament_level, - commitment.merkle.root_hash(), - ); - - self.arena_sender - .lock() - .await - .win_timeout_match( - match_state.tournament_address, - match_state.id, - left.root_hash(), - right.root_hash(), - ) - .await?; - } - Ok(()) - } - - #[async_recursion] - async fn react_sealed_match<'a>( - &mut self, - match_state: &MatchState, - commitment: &MachineCommitment, - tournament_level: u64, - tournament_max_level: u64, - tournament_states: &TournamentStateMap, - ) -> Result<()> { - if tournament_level == (tournament_max_level - 1) { - let (left, right) = commitment - .merkle - .subtrees() - .expect("merkle tree should have subtrees"); - - let proof = { - MachineInstance::get_logs( - &self.machine_path, - match_state.other_parent, - match_state.leaf_cycle, - &self.db, - )? - }; - - info!( - "win leaf match in tournament {} of level {} for commitment {}, proof size {}", - match_state.tournament_address, - tournament_level, - commitment.merkle.root_hash(), - proof.0.len() - ); - self.arena_sender - .lock() - .await - .win_leaf_match( - match_state.tournament_address, - match_state.id, - left.root_hash(), - right.root_hash(), - proof.0, - ) - .await?; - } else { - self.react_tournament( - Some(commitment), - match_state - .inner_tournament - .expect("inner tournament not found"), - tournament_states, - ) - .await?; - } - - Ok(()) - } - - async fn react_unsealed_match( - &mut self, - match_state: &MatchState, - commitment: &MachineCommitment, - tournament_level: u64, - tournament_max_level: u64, - ) -> Result<()> { - let Some(r) = commitment.merkle.find_child(&match_state.other_parent) else { - debug!("not my turn to react"); - return Ok(()); - }; - - let (left, right) = r.subtrees().expect("merkle tree should have subtrees"); - - let running_leaf_position = { - if left.root_hash() != match_state.left_node { - // disagree on left - match_state.running_leaf_position - } else { - // disagree on right - match_state.running_leaf_position + U256::one() - } - }; - - let agree_state_proof = if running_leaf_position.is_zero() { - MerkleProof::leaf(commitment.implicit_hash, U256::ZERO) - } else { - commitment - .merkle - .prove_leaf(running_leaf_position - U256::one()) - }; - - if tournament_level == (tournament_max_level - 1) { - info!( - "seal leaf match in tournament {} of level {} for commitment {}", - match_state.tournament_address, - tournament_level, - commitment.merkle.root_hash(), - ); - self.arena_sender - .lock() - .await - .seal_leaf_match( - match_state.tournament_address, - match_state.id, - left.root_hash(), - right.root_hash(), - &agree_state_proof, - ) - .await?; - } else { - info!( - "seal inner match in tournament {} of level {} for commitment {}", - match_state.tournament_address, - tournament_level, - commitment.merkle.root_hash(), - ); - self.arena_sender - .lock() - .await - .seal_inner_match( - match_state.tournament_address, - match_state.id, - left.root_hash(), - right.root_hash(), - &agree_state_proof, - ) - .await?; - } - Ok(()) - } - - async fn react_running_match( - &mut self, - match_state: &MatchState, - commitment: &MachineCommitment, - tournament_level: u64, - ) -> Result<()> { - let Some(r) = commitment.merkle.find_child(&match_state.other_parent) else { - debug!("not my turn to react"); - return Ok(()); - }; - - let (left, right) = r.subtrees().expect("merkle tree should have subtrees"); - - let (new_left, new_right) = if left.root_hash() != match_state.left_node { - debug!("going down to the left"); - left.subtrees().expect("left tree should have subtrees") - } else { - debug!("going down to the right"); - right.subtrees().expect("right tree should have subtrees") - }; - - info!( - "advance match with current height {} in tournament {} of level {} for commitment {}", - match_state.current_height, - match_state.tournament_address, - tournament_level, - commitment.merkle.root_hash(), - ); - self.arena_sender - .lock() - .await - .advance_match( - match_state.tournament_address, - match_state.id, - left.root_hash(), - right.root_hash(), - new_left.root_hash(), - new_right.root_hash(), - ) - .await?; - Ok(()) - } -} - -fn get_tournament_state(map: &TournamentStateMap, tournament_address: Address) -> &TournamentState { - map.get(&tournament_address) - .expect("tournament state not found") -} diff --git a/prt/client-rs/core/src/tournament/config.rs b/prt/client-rs/core/src/tournament/config.rs deleted file mode 100644 index e1d46df85..000000000 --- a/prt/client-rs/core/src/tournament/config.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Module for configuration of an Arena. -use std::{fmt, fs, path::PathBuf, str::FromStr}; - -use alloy::{ - network::{Ethereum, EthereumWallet, NetworkWallet}, - signers::local::PrivateKeySigner, -}; -use clap::{ArgGroup, Args, Parser}; - -const ANVIL_CHAIN_ID: u64 = 31337; -const ANVIL_URL: &str = "http://127.0.0.1:8545"; -pub const ANVIL_KEY_1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; - -#[derive(Parser, Debug, Clone)] -#[command(name = "blockchain_config")] -#[command(about = "Configuration for Blockchain Access")] -#[command(group = ArgGroup::new("auth") -.required(true) -.multiple(false))] -pub struct BlockchainConfig { - /// url to blockchain endpoint - #[arg(long, env, default_value = ANVIL_URL)] - pub web3_rpc_url: String, - /// chain id of the blockchain - #[arg(long, env, default_value_t = ANVIL_CHAIN_ID)] - pub web3_chain_id: u64, - /// private key of player's wallet - #[arg(long, env, group = "auth")] - pub web3_private_key: Option, - /// private key of player's wallet - #[arg(long, env, group = "auth")] - pub web3_private_key_file: Option, - #[command(flatten)] - pub aws_config: AWSConfig, -} - -#[derive(Args, Debug, Clone)] -pub struct AWSConfig { - /// aws kms key id (optional) - #[arg(long, env, group = "auth")] - pub aws_kms_key_id: Option, - /// aws kms key id (optional) - #[arg(long, env, group = "auth")] - pub aws_kms_key_id_file: Option, - /// aws endpoint url - #[arg(long, env)] - pub aws_endpoint_url: Option, - /// aws region - #[arg(long, env, default_value = "us-east-1")] - pub aws_region: String, -} - -impl BlockchainConfig { - pub fn initialize(&mut self) { - if self.aws_config.aws_endpoint_url.is_none() { - self.aws_config.aws_endpoint_url = Some(format!( - "https://kms.{}.amazonaws.com", - self.aws_config.aws_region - )); - } - - if let Some(file) = &self.web3_private_key_file { - self.web3_private_key = Some( - fs::read_to_string(file) - .expect("fail to read key from file") - .lines() - .next() - .unwrap_or("") - .trim() - .to_string(), - ); - } - if let Some(file) = &self.aws_config.aws_kms_key_id_file { - self.aws_config.aws_kms_key_id = Some( - fs::read_to_string(file) - .expect("fail to read key from kws file") - .lines() - .next() - .unwrap_or("") - .trim() - .to_string(), - ); - } - } -} - -impl fmt::Display for BlockchainConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, " Web3 RPC URL: {}", self.web3_rpc_url)?; - writeln!(f, " Web3 Chain ID: {}", self.web3_chain_id)?; - - if self.web3_private_key_file.is_some() { - writeln!( - f, - " Web3 Private Key File: {:?}", - self.web3_private_key_file - )?; - let wallet = get_wallet_from_private(&self.web3_private_key.as_deref().unwrap()); - writeln!( - f, - " Wallet Public Address: {}", - >::default_signer_address(&wallet) - )?; - } else if self.web3_private_key.is_some() { - let wallet = get_wallet_from_private(&self.web3_private_key.as_deref().unwrap()); - writeln!( - f, - " Wallet Public Address: {}", - >::default_signer_address(&wallet) - )?; - writeln!(f, " Web3 Private Key: [REDACTED]")?; - } - - writeln!(f, " AWS Config:")?; - write!(f, "{}", self.aws_config)?; - - Ok(()) - } -} - -impl fmt::Display for AWSConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.aws_kms_key_id_file.is_some() { - writeln!( - f, - " AWS KMS Key ID File: {:?}", - self.aws_kms_key_id_file - )?; - } else if self.aws_kms_key_id.is_some() { - writeln!(f, " AWS KMS Key ID: [REDACTED]")?; - } - - if let Some(ref endpoint) = self.aws_endpoint_url { - writeln!(f, " AWS Endpoint URL: {}", endpoint)?; - } else { - writeln!(f, " AWS Endpoint URL: ")?; - } - - writeln!(f, " AWS Region: {}", self.aws_region)?; - Ok(()) - } -} - -pub fn get_wallet_from_private(web3_private_key: &str) -> EthereumWallet { - let signer = - PrivateKeySigner::from_str(web3_private_key).expect("could not create private key signer"); - - EthereumWallet::from(signer) -} diff --git a/prt/client-rs/core/src/tournament/reader.rs b/prt/client-rs/core/src/tournament/reader.rs deleted file mode 100644 index c2c3ee968..000000000 --- a/prt/client-rs/core/src/tournament/reader.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! This module defines the struct [StateReader] that is responsible for the reading the states -//! of tournaments - -use anyhow::{Result, anyhow}; -use async_recursion::async_recursion; -use std::collections::HashMap; - -use alloy::{ - contract::{Error, Event}, - eips::BlockNumberOrTag::Latest, - providers::{DynProvider, Provider}, - rpc::types::{Log, Topic}, - sol_types::SolEvent, - sol_types::private::{Address, B256}, -}; - -use crate::tournament::{ - ClockState, CommitmentState, MatchID, MatchState, TournamentState, TournamentStateMap, - TournamentWinner, -}; -use cartesi_dave_merkle::Digest; -use cartesi_prt_contracts::tournament; - -#[derive(Clone)] -pub struct StateReader { - client: DynProvider, - block_created_number: u64, - long_block_range_error_codes: Vec, -} - -impl StateReader { - pub fn new( - client: DynProvider, - block_created_number: u64, - long_block_range_error_codes: Vec, - ) -> Result { - Ok(Self { - client, - block_created_number, - long_block_range_error_codes, - }) - } - - async fn latest_block_number(&self) -> Result { - let block_number = self - .client - .get_block(Latest.into()) - .await? - .expect("cannot get last block") - .header - .number; - - Ok(block_number) - } - - async fn query_events( - &self, - topic1: Option<&Topic>, - read_from: &Address, - ) -> Result> { - let latest_block = self.latest_block_number().await?; - - if latest_block < self.block_created_number { - return Ok(vec![]); - } - - get_events( - &self.client, - topic1, - read_from, - self.block_created_number, - latest_block, - &self.long_block_range_error_codes, - ) - .await - .map_err(|errors| anyhow!("{errors:?}")) - } - - async fn created_tournament( - &self, - tournament_address: Address, - match_id: MatchID, - ) -> Result> { - let topic1: Topic = B256::from(match_id.hash()).into(); - let events = self - .query_events::( - Some(&topic1), - &tournament_address, - ) - .await?; - - if let Some((event, _)) = events.last() { - Ok(Some(TournamentCreatedEvent { - parent_match_id_hash: match_id.hash(), - new_tournament_address: event.childTournament, - })) - } else { - Ok(None) - } - } - - async fn capture_matches(&self, tournament_address: Address) -> Result> { - let tournament = tournament::Tournament::new(tournament_address, &self.client); - let created_matches = self.created_matches(tournament_address).await?; - - let mut matches = vec![]; - for match_event in created_matches { - let match_id = match_event.id; - let m = tournament.getMatch(match_id.hash().into()).call().await?; - - if m.isInit { - let leaf_cycle = tournament - .getMatchCycle(match_id.hash().into()) - .call() - .await?; - let running_leaf_position = m.runningLeafPosition; - - let match_state = MatchState { - id: match_id, - other_parent: m.otherParent.into(), - left_node: m.leftNode.into(), - right_node: m.rightNode.into(), - running_leaf_position, - current_height: m.currentHeight, - tournament_address, - leaf_cycle, - inner_tournament: None, - }; - matches.push(match_state); - } - } - - Ok(matches) - } - - async fn created_matches(&self, tournament_address: Address) -> Result> { - let events: Vec = self - .query_events::(None, &tournament_address) - .await? - .iter() - .map(|(event, _)| MatchCreatedEvent { - id: MatchID { - commitment_one: event.one.into(), - commitment_two: event.two.into(), - }, - left_hash: event.leftOfTwo.into(), - }) - .collect(); - Ok(events) - } - - async fn joined_commitments( - &self, - tournament_address: Address, - ) -> Result> { - let events = self - .query_events::(None, &tournament_address) - .await? - .iter() - .map(|(event, _)| CommitmentJoinedEvent { - root: event.commitment.into(), - }) - .collect(); - Ok(events) - } - - async fn get_commitment( - &self, - tournament_address: Address, - commitment_hash: Digest, - ) -> Result { - let tournament = tournament::Tournament::new(tournament_address, &self.client); - let commitment_return = tournament - .getCommitment(commitment_hash.into()) - .call() - .await?; - - let block_number = self - .client - .get_block(Latest.into()) - .await? - .expect("cannot get last block") - .header - .number; - let clock_state = ClockState { - allowance: commitment_return._0.allowance, - start_instant: commitment_return._0.startInstant, - block_number, - }; - Ok(CommitmentState { - clock: clock_state, - final_state: commitment_return._1.into(), - latest_match: None, - }) - } - - pub async fn fetch_from_root( - &self, - root_tournament_address: Address, - ) -> Result { - let mut states = HashMap::new(); - self.fetch_tournament( - TournamentState::new_root(root_tournament_address), - &mut states, - ) - .await?; - - Ok(states) - } - - #[async_recursion] - async fn fetch_tournament( - &self, - mut state: TournamentState, - states: &mut TournamentStateMap, - ) -> Result<()> { - let tournament_address = state.address; - let tournament = tournament::Tournament::new(tournament_address, &self.client); - let level_constants_return = tournament.tournamentLevelConstants().call().await?; - ( - state.max_level, - state.level, - state.log2_stride, - state.log2_stride_count, - ) = ( - level_constants_return._maxLevel, - level_constants_return._level, - level_constants_return._log2step, - level_constants_return._height, - ); - - assert!(state.level < state.max_level, "level out of bounds"); - - if state.level > 0 { - let tournament = tournament::Tournament::new(tournament_address, &self.client); - state.can_be_eliminated = tournament.canBeEliminated().call().await?; - } - - let mut captured_matches = self.capture_matches(tournament_address).await?; - let commitments_joined = self.joined_commitments(tournament_address).await?; - - let mut commitment_states = HashMap::new(); - for commitment in commitments_joined { - let commitment_state = self - .get_commitment(tournament_address, commitment.root) - .await?; - commitment_states.insert(commitment.root, commitment_state); - } - - for (i, captured_match) in captured_matches.iter_mut().enumerate() { - self.fetch_match(captured_match, states, state.level) - .await?; - - commitment_states - .get_mut(&captured_match.id.commitment_one) - .expect("cannot find commitment one state") - .latest_match = Some(i); - commitment_states - .get_mut(&captured_match.id.commitment_two) - .expect("cannot find commitment two state") - .latest_match = Some(i); - } - - let winner = match state.parent { - Some(_) => self.tournament_winner(tournament_address).await?, - None => self.root_tournament_winner(tournament_address).await?, - }; - - state.winner = winner; - state.matches = captured_matches; - state.commitment_states = commitment_states; - - states.insert(tournament_address, state); - - Ok(()) - } - - #[async_recursion] - async fn fetch_match( - &self, - match_state: &mut MatchState, - states: &mut TournamentStateMap, - tournament_level: u64, - ) -> Result<()> { - let created_tournament = self - .created_tournament(match_state.tournament_address, match_state.id) - .await?; - if let Some(inner) = created_tournament { - let inner_tournament = TournamentState::new_inner( - inner.new_tournament_address, - tournament_level, - match_state.leaf_cycle, - match_state.tournament_address, - ); - self.fetch_tournament(inner_tournament, states).await?; - match_state.inner_tournament = Some(inner.new_tournament_address); - - return Ok(()); - } - - Ok(()) - } - - async fn root_tournament_winner( - &self, - root_tournament_address: Address, - ) -> Result> { - let root_tournament = tournament::Tournament::new(root_tournament_address, &self.client); - let arbitration_result_return = root_tournament.arbitrationResult().call().await?; - let (finished, commitment, state) = ( - arbitration_result_return._0, - arbitration_result_return._1, - arbitration_result_return._2, - ); - - if finished { - Ok(Some(TournamentWinner::Root( - commitment.into(), - state.into(), - ))) - } else { - Ok(None) - } - } - - async fn tournament_winner( - &self, - tournament_address: Address, - ) -> Result> { - let tournament = tournament::Tournament::new(tournament_address, &self.client); - let inner_tournament_winner_return = tournament.innerTournamentWinner().call().await?; - let (finished, parent_commitment, dangling_commitment) = ( - inner_tournament_winner_return._0, - inner_tournament_winner_return._1, - inner_tournament_winner_return._2, - ); - - if finished { - Ok(Some(TournamentWinner::Inner( - parent_commitment.into(), - dangling_commitment.into(), - ))) - } else { - Ok(None) - } - } -} - -/// This struct is used to communicate the creation of a new tournament. -#[derive(Clone, Copy)] -pub struct TournamentCreatedEvent { - pub parent_match_id_hash: Digest, - pub new_tournament_address: Address, -} - -/// This struct is used to communicate the enrollment of a new commitment. -#[derive(Clone, Copy)] -pub struct CommitmentJoinedEvent { - pub root: Digest, -} - -/// This struct is used to communicate the creation of a new match. -#[derive(Clone, Copy)] -pub struct MatchCreatedEvent { - pub id: MatchID, - pub left_hash: Digest, -} - -// Below is a simplified version originated from https://github.com/cartesi/state-fold -// ParitionProvider will attempt to fetch events in smaller partition if the original request is too large -#[async_recursion] -async fn get_events( - provider: &impl Provider, - topic1: Option<&Topic>, - read_from: &Address, - start_block: u64, - end_block: u64, - long_block_range_error_codes: &Vec, -) -> std::result::Result, Vec> { - let event: Event<_, _, _> = { - let mut e = Event::new_sol(provider, read_from) - .from_block(start_block) - .to_block(end_block) - .event(E::SIGNATURE); - - if let Some(t) = topic1 { - e = e.topic1(t.clone()); - } - - e - }; - - match event.query().await { - Ok(l) => Ok(l), - Err(e) => { - if should_retry_with_partition(&e, long_block_range_error_codes) { - let middle = { - let blocks = 1 + end_block - start_block; - let half = blocks / 2; - start_block + half - 1 - }; - - let first_res = get_events( - provider, - topic1, - read_from, - start_block, - middle, - long_block_range_error_codes, - ) - .await; - - let second_res = get_events( - provider, - topic1, - read_from, - middle + 1, - end_block, - long_block_range_error_codes, - ) - .await; - - match (first_res, second_res) { - (Ok(mut first), Ok(second)) => { - first.extend(second); - Ok(first) - } - - (Err(mut first), Err(second)) => { - first.extend(second); - Err(first) - } - - (Err(err), _) | (_, Err(err)) => Err(err), - } - } else { - Err(vec![e]) - } - } - } -} - -fn should_retry_with_partition( - err: &impl std::error::Error, - long_block_range_error_codes: &Vec, -) -> bool { - for code in long_block_range_error_codes { - let s = format!("{:?}", err); - if s.contains(&code.to_string()) { - return true; - } - } - - false -} diff --git a/prt/client-rs/core/src/tournament/tournament.rs b/prt/client-rs/core/src/tournament/tournament.rs deleted file mode 100644 index b1c1d6503..000000000 --- a/prt/client-rs/core/src/tournament/tournament.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! This module defines the structs that are used for the interacting to tournaments - -use crate::machine::MachineCommitment; - -use alloy::primitives::Address; -use cartesi_dave_merkle::Digest; -use ruint::aliases::U256; -use std::collections::HashMap; - -pub type TournamentStateMap = HashMap; -pub type CommitmentMap = HashMap; - -/// Struct used to identify a match. -#[derive(Clone, Copy, Debug)] -pub struct MatchID { - pub commitment_one: Digest, - pub commitment_two: Digest, -} - -impl MatchID { - /// Generates a new [Digest] - pub fn hash(&self) -> Digest { - self.commitment_one.join(&self.commitment_two) - } -} - -// TODO: this can be optimized if the bindings generated with only one shared `Id` struct -impl From for cartesi_prt_contracts::tournament::Match::Id { - fn from(match_id: MatchID) -> Self { - cartesi_prt_contracts::tournament::Match::Id { - commitmentOne: match_id.commitment_one.into(), - commitmentTwo: match_id.commitment_two.into(), - } - } -} - -/// Struct used to communicate the state of a commitment. -#[derive(Clone, Copy, Debug)] -pub struct CommitmentState { - pub clock: ClockState, - pub final_state: Digest, - pub latest_match: Option, -} - -/// Struct used to communicate the state of a clock. -#[derive(Clone, Copy, Debug)] -pub struct ClockState { - pub allowance: u64, - pub start_instant: u64, - pub block_number: u64, -} - -impl ClockState { - pub fn has_time(&self) -> bool { - if self.start_instant == 0 { - true - } else { - self.deadline() > self.block_number - } - } - - pub fn time_since_timeout(&self) -> u64 { - if self.start_instant == 0 { - 0 - } else { - self.block_number - self.deadline() - } - } - - // deadline of clock if it's ticking - fn deadline(&self) -> u64 { - self.start_instant + self.allowance - } -} - -impl std::fmt::Display for ClockState { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if self.start_instant == 0 { - write!(f, "clock paused, {} blocks left", self.allowance) - } else { - let time_elapsed = self.block_number - self.start_instant; - if self.allowance >= time_elapsed { - write!( - f, - "clock ticking, {} blocks left", - self.allowance - time_elapsed - ) - } else { - write!( - f, - "clock ticking, {} blocks overdue", - time_elapsed - self.allowance - ) - } - } - } -} - -/// Enum used to represent the winner of a tournament. -#[derive(Clone, PartialEq, Debug)] -pub enum TournamentWinner { - Root(Digest, Digest), - Inner(Digest, Digest), -} - -/// Struct used to communicate the state of a tournament. -#[derive(Clone, Default, Debug)] -pub struct TournamentState { - pub address: Address, - pub base_cycle: U256, - pub level: u64, - pub log2_stride: u64, - pub log2_stride_count: u64, - pub max_level: u64, - pub parent: Option
, - pub commitment_states: HashMap, - pub matches: Vec, - pub winner: Option, - pub can_be_eliminated: bool, -} - -impl TournamentState { - pub fn new_root(address: Address) -> Self { - TournamentState { - address, - ..Default::default() - } - } - - pub fn new_inner(address: Address, level: u64, base_cycle: U256, parent: Address) -> Self { - TournamentState { - address, - base_cycle, - level: level + 1, - parent: Some(parent), - ..Default::default() - } - } -} - -/// Struct used to communicate the state of a match. -#[derive(Clone, Copy, Debug)] -pub struct MatchState { - pub id: MatchID, - pub other_parent: Digest, - pub left_node: Digest, - pub right_node: Digest, - pub running_leaf_position: U256, - pub current_height: u64, - pub leaf_cycle: U256, - pub tournament_address: Address, - pub inner_tournament: Option
, -} From 34b50b2e90073c6f255843d126a27a3bc95b8406 Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Mon, 20 Jul 2026 08:37:45 -0300 Subject: [PATCH 031/113] test(e2e): rework the Lua harness - scenarios, battery, primitives 23 scenarios (echo, the honeypot stf suite, kill/respawn lifecycle points, multi_sybil's concurrent matches, kill_join), orchestrated on hardened primitives: drive loops advance one block per poll, Env.fast_forward is chess-clock-safe (sleep first), sybils auto-allocate signing accounts and refuse the node's, scenario deadlines convert hangs to failures. battery.sh runs the suite in lanes and records power provenance. The env deploys with the node's wallet as the sole sentry (claim staging period 1000), so every scenario exercises the staged protocol's claim-agreement fast path. test_env's run_epoch records the chain after settle (RECORD_CHAIN_FIXTURE) - the capture hook for the node's fold fixtures. The Lua client stays the independent commitment oracle; the harness reads the node's claim from settlement_info, never from node internals. --- prt/client-lua/README.md | 25 ++ prt/client-lua/computation/commitment.lua | 8 +- prt/client-lua/computation/machine.lua | 86 +++- prt/client-lua/cryptography/merkle_tree.lua | 2 +- prt/client-lua/utils/color.lua | 12 +- prt/client-lua/utils/flat.lua | 113 ----- prt/client-lua/utils/json.lua | 388 ------------------ prt/client-lua/utils/time.lua | 22 + prt/tests/common/blockchain/constants.lua | 9 +- prt/tests/common/blockchain/node.lua | 25 +- .../runners/helpers/fake_commitment.lua | 155 ------- prt/tests/common/runners/hero_runner.lua | 34 -- prt/tests/common/runners/idle_runner.lua | 23 -- prt/tests/common/runners/rust_hero_runner.lua | 115 ------ prt/tests/common/runners/sybil_runner.lua | 18 +- prt/tests/common/test_utils/example.lua | 137 ------- prt/tests/common/test_utils/test.lua | 4 - prt/tests/rollups/.gitignore | 5 +- prt/tests/rollups/README.md | 32 +- prt/tests/rollups/battery.sh | 114 +++++ prt/tests/rollups/dave/node.lua | 215 +++++++--- prt/tests/rollups/dave/reader.lua | 6 +- prt/tests/rollups/justfile | 75 +++- prt/tests/rollups/sepolia/dispute.lua | 3 +- prt/tests/rollups/test_cases/big_input.lua | 8 +- prt/tests/rollups/test_cases/chaos.lua | 49 +++ prt/tests/rollups/test_cases/kill_catchup.lua | 35 ++ .../test_cases/kill_catchup_batched.lua | 37 ++ .../test_cases/kill_commitment_build.lua | 48 +++ prt/tests/rollups/test_cases/kill_join.lua | 59 +++ .../rollups/test_cases/kill_mid_match.lua | 56 +++ prt/tests/rollups/test_cases/kill_settle.lua | 40 ++ prt/tests/rollups/test_cases/multi_sybil.lua | 132 ++++++ prt/tests/rollups/test_cases/stf_all.lua | 51 ++- prt/tests/rollups/test_cases/stf_revert.lua | 49 +++ prt/tests/rollups/test_env.lua | 170 ++++++-- test/programs/.gitignore | 4 + test/programs/justfile | 13 + 38 files changed, 1266 insertions(+), 1111 deletions(-) create mode 100644 prt/client-lua/README.md delete mode 100644 prt/client-lua/utils/flat.lua delete mode 100644 prt/client-lua/utils/json.lua delete mode 100644 prt/tests/common/runners/helpers/fake_commitment.lua delete mode 100755 prt/tests/common/runners/hero_runner.lua delete mode 100755 prt/tests/common/runners/idle_runner.lua delete mode 100644 prt/tests/common/runners/rust_hero_runner.lua delete mode 100644 prt/tests/common/test_utils/example.lua delete mode 100644 prt/tests/common/test_utils/test.lua create mode 100755 prt/tests/rollups/battery.sh create mode 100644 prt/tests/rollups/test_cases/chaos.lua create mode 100644 prt/tests/rollups/test_cases/kill_catchup.lua create mode 100644 prt/tests/rollups/test_cases/kill_catchup_batched.lua create mode 100644 prt/tests/rollups/test_cases/kill_commitment_build.lua create mode 100644 prt/tests/rollups/test_cases/kill_join.lua create mode 100644 prt/tests/rollups/test_cases/kill_mid_match.lua create mode 100644 prt/tests/rollups/test_cases/kill_settle.lua create mode 100644 prt/tests/rollups/test_cases/multi_sybil.lua create mode 100644 prt/tests/rollups/test_cases/stf_revert.lua diff --git a/prt/client-lua/README.md b/prt/client-lua/README.md new file mode 100644 index 000000000..195a7fb03 --- /dev/null +++ b/prt/client-lua/README.md @@ -0,0 +1,25 @@ +# PRT client (Lua) + +The Lua implementation of the PRT client. It predates the Rust client and +now serves three roles: + +1. Reference implementation: an independent, readable implementation of + commitment construction (`computation/`) and the honest dispute + strategy (`player/`). The e2e tests cross-check the Rust node against + it every epoch. +2. Test actor: the sybil (dishonest) players in `prt/tests/` are this + client's honest strategy driven with a patched commitment builder. +3. Executable documentation: when the Rust code is unclear, this is + usually the fastest way to understand the intended behavior. + +Layout: + +- `computation/` - machine driving and commitment building (the Lua twin + of `cartesi-rollups/node/src/machine/`). +- `player/` - honest strategy, tournament state fetching, tx sending. +- `cryptography/` - keccak hashing and incremental merkle builders. +- `utils/` - process and time helpers used by the test harness. + +Requires Lua 5.4, a local Cartesi Machine installation, and `cast` +(foundry) on the PATH. It is exercised through the test suites (see +`docs/test-harness.md`), not as a standalone daemon. diff --git a/prt/client-lua/computation/commitment.lua b/prt/client-lua/computation/commitment.lua index c13251aa8..2819d74ec 100644 --- a/prt/client-lua/computation/commitment.lua +++ b/prt/client-lua/computation/commitment.lua @@ -2,11 +2,9 @@ local MerkleBuilder = require "cryptography.merkle_builder" local Machine = require "computation.machine" local conversion = require "utils.conversion" -local cartesi = require "cartesi" local arithmetic = require "utils.arithmetic" local consts = require "computation.constants" local uint256 = require "utils.bint" (256) -local helper = require "utils.helper" local ulte = arithmetic.ulte @@ -42,7 +40,7 @@ local function run_uarch_span(machine) -- 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_state = machine:ureset() + machine:ureset() -- Check if machine is yielded and handle revert if needed if machine:is_yielded() then @@ -70,8 +68,8 @@ local function build_small_machine_commitment(log2_stride_count, machine, initia -- Optional optimization, just comment to remove. if machine_state.halted or machine_state.yielded then - uarch_span, _ = run_uarch_span(machine) - builder:add(uarch_span, instruction_count - instruction + 1) + local last_span = run_uarch_span(machine) + builder:add(last_span, instruction_count - instruction + 1) break end end diff --git a/prt/client-lua/computation/machine.lua b/prt/client-lua/computation/machine.lua index 655d883aa..a99b37a0b 100644 --- a/prt/client-lua/computation/machine.lua +++ b/prt/client-lua/computation/machine.lua @@ -51,7 +51,17 @@ Machine.__index = Machine local machine_settings = { htif = { no_console_putchar = true } } -function Machine:new_from_path(path) +-- Default home for revert snapshots (the hash-named machine stores +-- feed_input writes): a run-local scratch directory. The old default +-- put them next to the source image, littering shared program +-- directories (test/programs/) and risking collisions between +-- parallel runs; TEST_INSTANCE keeps the scratch disjoint the same +-- way it does the harness's other working-dir singletons. Exported so +-- the harness can clear it at scenario start. +Machine.default_snapshot_scratch = "_machine_scratch" + .. (os.getenv("TEST_INSTANCE") and ("-" .. os.getenv("TEST_INSTANCE")) or "") + +function Machine:new_from_path(path, snapshot_dir) local machine = cartesi.machine(path, machine_settings) local start_cycle = machine:read_reg("mcycle") @@ -59,8 +69,13 @@ function Machine:new_from_path(path) -- Validators must verify this first assert(machine:read_reg("uarch_cycle") == 0) - -- Derive snapshot_dir from path's parent directory - local snapshot_dir = path:match("(.*)/[^/]*$") or "/dispute/snapshots" + -- Revert snapshots go to the run-local scratch unless the caller + -- provides a dedicated directory (callers own their dir's + -- lifecycle; the default's parent is ensured here). + if not snapshot_dir then + snapshot_dir = Machine.default_snapshot_scratch + os.execute("mkdir -p " .. snapshot_dir) + end local b = { machine = machine, @@ -130,7 +145,7 @@ local function advance_rollup(self, meta_cycle, inputs) end function Machine:new_rollup_advanced_until(path, meta_cycle, inputs) - local machine = Machine:new_from_path(path) + local machine = self:new_from_path(path) advance_rollup(machine, meta_cycle, inputs) return machine end @@ -156,33 +171,48 @@ local function process_input(machine, log2_stride) end end -function Machine.root_rollup_commitment(pristine_path, log2_stride, inputs) - local machine = Machine:new_from_path(pristine_path) - assert(machine:is_yielded()) +-- Computes one epoch's commitment from the machine's current state, +-- advancing it through the epoch. A lineage can call this repeatedly, +-- epoch after epoch, without ever touching foreign snapshots. +function Machine:rollup_commitment(log2_stride, inputs) + assert(self:is_yielded()) assert(consts.log2_barch_span_to_input > (log2_stride - consts.log2_uarch_span_to_barch)) local max_input_count = 1 << (consts.log2_input_span_to_epoch) local builder = MerkleBuilder:new() - local state = machine:state() - local initial_hash = state.root_hash + local initial_hash = self:state().root_hash local input_i = 0 + local processing_bigs = {} while input_i < max_input_count do if inputs[input_i + 1] then local input_bin = conversion.bin_from_hex_n(inputs[input_i + 1]) - machine:feed_input(input_bin); - local tree = process_input(machine, log2_stride) + self:feed_input(input_bin); + local tree = process_input(self, log2_stride) builder:add(tree) input_i = input_i + 1 + -- Big cycles input_i consumed; scenarios use it to aim + -- patch chains at the revert closing slot. + processing_bigs[input_i] = self._last_input_bigs else - local tree = process_input(machine, log2_stride) + local tree = process_input(self, log2_stride) builder:add(tree, max_input_count - input_i) break end end - return initial_hash, builder:build(initial_hash) + return initial_hash, builder:build(initial_hash), processing_bigs +end + +function Machine.root_rollup_commitment(pristine_path, log2_stride, inputs) + local machine = Machine:new_from_path(pristine_path) + return machine:rollup_commitment(log2_stride, inputs) +end + +-- Store the current machine as a new snapshot directory. +function Machine:store_to(path) + self.machine:store(path) end function Machine:state() @@ -231,6 +261,9 @@ function Machine:feed_input(input_bin) end self.snapshot_path = new_snapshot_path + -- Marks the window start so the yield below can report how many + -- big cycles the input consumed. + self._input_start_cycle = self:physical_cycle() self:write_checkpoint(self.machine:get_root_hash()) self.machine:send_cmio_response(cartesi.CMIO_YIELD_REASON_ADVANCE_STATE, input_bin); end @@ -247,6 +280,13 @@ function Machine:run(cycle) self:physical_cycle() == target_physical_cycle if self:is_yielded() then + -- Captured before the revert reloads the snapshot: the big + -- cycle count the input consumed, yield instruction included. + -- The revert of a rejected input lands at the closing slot of + -- big cycle (this count - 1) of its window. + if self._input_start_cycle then + self._last_input_bigs = self:physical_cycle() - self._input_start_cycle + end self:revert_if_needed() end self.cycle = cycle @@ -258,14 +298,23 @@ function Machine:revert_if_needed() -- revert if needed only when machine yields assert(self:is_yielded()) - -- we check if the request is accepted - -- if it is not, we revert the machine state to previous snapshot + -- The on-chain closing slot restores the checkpoint ONLY on + -- RX_REJECTED (AdvanceStatus + revertIfNeeded): an exception + -- yield keeps the exception state, and any other manual reason + -- has no defined transition on-chain. Solidity is the source of + -- truth; treating every non-accept as a revert was a consensus + -- mismatch shared with the node (found 2026-07-15). local _, reason, _ = self.machine:receive_cmio_request() - if reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED then + if reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED then -- Revert to previous snapshot print("revert to previous snapshot") local machine = cartesi.machine(self.snapshot_path, machine_settings) self.machine = machine + elseif + reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED + and reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION + then + error(string.format("manual yield reason %d has no defined state transition", reason)) end end @@ -281,8 +330,11 @@ function Machine:prove_revert_if_needed() local to_host_proof = self:prove_read_word(to_host_address) proof = proof .. to_host_proof + -- The chain consumes the checkpoint leaf only on the REJECTED + -- branch (getRevertRootHash); an exception yield reads nothing + -- more. local _, reason, _ = self.machine:receive_cmio_request() - if reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED then + if reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED then local checkpoint_proof = self:prove_read_leaf(consts.CHECKPOINT_ADDRESS) proof = proof .. checkpoint_proof end diff --git a/prt/client-lua/cryptography/merkle_tree.lua b/prt/client-lua/cryptography/merkle_tree.lua index 3c50fb0aa..db00abb98 100644 --- a/prt/client-lua/cryptography/merkle_tree.lua +++ b/prt/client-lua/cryptography/merkle_tree.lua @@ -29,7 +29,7 @@ function MerkleTree:new(leafs, root_hash, log2size, implicit_hash) height = height, implicit_hash = implicit_hash, } - setmetatable(m, MerkleTree) + setmetatable(m, self) return m end diff --git a/prt/client-lua/utils/color.lua b/prt/client-lua/utils/color.lua index c188228c0..ed0bf37e3 100644 --- a/prt/client-lua/utils/color.lua +++ b/prt/client-lua/utils/color.lua @@ -10,7 +10,8 @@ -- 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(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() -- @@ -67,15 +68,15 @@ 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' + color.bg[name] = esc .. tostring(100+i-1) .. 'm' end local function fg256(_,n) - return esc .. "38;5;" .. n .. 'm' + return esc .. "38;5;" .. n .. 'm' end local function bg256(_,n) - return esc .. "48;5;" .. n .. 'm' + return esc .. "48;5;" .. n .. 'm' end setmetatable(color.fg, {__call = fg256}) @@ -126,7 +127,8 @@ function color.test() 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(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 diff --git a/prt/client-lua/utils/flat.lua b/prt/client-lua/utils/flat.lua deleted file mode 100644 index 7d57c453c..000000000 --- a/prt/client-lua/utils/flat.lua +++ /dev/null @@ -1,113 +0,0 @@ -local bint = require 'utils.bint' (256) -- use 256 bits integers - -local m = {} - -local function print_table(object) - if type(object) == "table" then - for k, v in pairs(object) do - print(string.format("\"%s\":{", k)) - print_table(v) - print(string.format("},", k)) - end - else - print(string.format("\"%s\"", object)) - end -end - --- this is a very specific flatten implementation for tournament tables --- it handles circular references and all custom classes being used -local function flatten_recursive(object, flat_tables) - if type(object) == "table" then - local id - if next(object) == nil then - return "nil" - -- object is empty - elseif object.address then - -- tournament table - id = tostring(object.address) - elseif object.match_id_hash then - -- match table - id = tostring(object.match_id_hash) - elseif object.hex_string then - -- merkle table, treat as hex_string - return object:hex_string() - elseif bint.isbint(object) then - -- bint, treat as string - return tostring(object) - elseif #object > 0 then - -- this is an array - local flatten = {} - for i = 1, #object do - flatten[i] = flatten_recursive(object[i], flat_tables) - end - return flatten - else - -- other kind of tables - id = ("%p"):format(object) - end - - if not flat_tables[id] then - local flat_table = {} - flat_tables[id] = flat_table - for k, v in pairs(object) do - -- key must be string - flat_table[tostring(k)] = flatten_recursive(v, flat_tables) - end - end - - if object.address then - -- tournament table, return only id to avoid circular references - return id - else - return flat_tables[id] - end - else - -- primitive types, return directly - return object - end -end - -function m.flatten(object) - local flat_tables = {} - local flat_object = flatten_recursive(object, flat_tables) - -- print_table(flat_tables) - return { - flat_tables = flat_tables, - flat_object = flat_object, - } -end - -local function create_table_stubs(flat_tables) - local tables = {} - for id in pairs(flat_tables) do - tables[id] = {} - end - return tables -end - -local function inflate_object(flat_object, tables) - if type(flat_object) == "table" then - local id = assert(flat_object.id, "missing id") - return tables[id] - else - return flat_object - end -end - -local function link_tables(flat_tables, tables) - for id, flat_table in pairs(flat_tables) do - for _, pair in ipairs(flat_table) do - local k = inflate_object(pair.key, tables) - local v = inflate_object(pair.value, tables) - tables[id][k] = v - end - end -end - -function m.inflate(t) - local tables = create_table_stubs(t.flat_tables) - link_tables(t.flat_tables, tables) - return inflate_object(t.flat_object, tables) -end - -return m diff --git a/prt/client-lua/utils/json.lua b/prt/client-lua/utils/json.lua deleted file mode 100644 index ecaef3f02..000000000 --- a/prt/client-lua/utils/json.lua +++ /dev/null @@ -1,388 +0,0 @@ --- --- json.lua --- --- Copyright (c) 2020 rxi --- --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies --- of the Software, and to permit persons to whom the Software is furnished to do --- so, subject to the following conditions: --- --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --- SOFTWARE. --- - -local json = { _version = "0.1.2" } - -------------------------------------------------------------------------------- --- Encode -------------------------------------------------------------------------------- - -local encode - -local escape_char_map = { - [ "\\" ] = "\\", - [ "\"" ] = "\"", - [ "\b" ] = "b", - [ "\f" ] = "f", - [ "\n" ] = "n", - [ "\r" ] = "r", - [ "\t" ] = "t", -} - -local escape_char_map_inv = { [ "/" ] = "/" } -for k, v in pairs(escape_char_map) do - escape_char_map_inv[v] = k -end - - -local function escape_char(c) - return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) -end - - -local function encode_nil(_) - return "null" -end - - -local function encode_table(val, stack) - local res = {} - stack = stack or {} - - -- Circular reference? - if stack[val] then error("circular reference") end - - stack[val] = true - - if rawget(val, 1) ~= nil or next(val) == nil then - -- Treat as array -- check keys are valid and it is not sparse - local n = 0 - for k in pairs(val) do - if type(k) ~= "number" then - error("invalid table: mixed or invalid key types") - end - n = n + 1 - end - if n ~= #val then - error("invalid table: sparse array") - end - -- Encode - for _, v in ipairs(val) do - table.insert(res, encode(v, stack)) - end - stack[val] = nil - return "[" .. table.concat(res, ",") .. "]" - - else - -- Treat as an object - for k, v in pairs(val) do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) - end - stack[val] = nil - return "{" .. table.concat(res, ",") .. "}" - end -end - - -local function encode_string(val) - return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' -end - - -local function encode_number(val) - -- Check for NaN, -inf and inf - if val ~= val or val <= -math.huge or val >= math.huge then - error("unexpected number value '" .. tostring(val) .. "'") - end - return string.format("%.14g", val) -end - - -local type_func_map = { - [ "nil" ] = encode_nil, - [ "table" ] = encode_table, - [ "string" ] = encode_string, - [ "number" ] = encode_number, - [ "boolean" ] = tostring, -} - - -encode = function(val, stack) - local t = type(val) - local f = type_func_map[t] - if f then - return f(val, stack) - end - error("unexpected type '" .. t .. "'") -end - - -function json.encode(val) - return ( encode(val) ) -end - - -------------------------------------------------------------------------------- --- Decode -------------------------------------------------------------------------------- - -local parse - -local function create_set(...) - local res = {} - for i = 1, select("#", ...) do - res[ select(i, ...) ] = true - end - return res -end - -local space_chars = create_set(" ", "\t", "\r", "\n") -local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") -local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") -local literals = create_set("true", "false", "null") - -local literal_map = { - [ "true" ] = true, - [ "false" ] = false, - [ "null" ] = nil, -} - - -local function next_char(str, idx, set, negate) - for i = idx, #str do - if set[str:sub(i, i)] ~= negate then - return i - end - end - return #str + 1 -end - - -local function decode_error(str, idx, msg) - local line_count = 1 - local col_count = 1 - for i = 1, idx - 1 do - col_count = col_count + 1 - if str:sub(i, i) == "\n" then - line_count = line_count + 1 - col_count = 1 - end - end - error( string.format("%s at line %d col %d", msg, line_count, col_count) ) -end - - -local function codepoint_to_utf8(n) - -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa - local f = math.floor - if n <= 0x7f then - return string.char(n) - elseif n <= 0x7ff then - return string.char(f(n / 64) + 192, n % 64 + 128) - elseif n <= 0xffff then - return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) - elseif n <= 0x10ffff then - return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, - f(n % 4096 / 64) + 128, n % 64 + 128) - end - error( string.format("invalid unicode codepoint '%x'", n) ) -end - - -local function parse_unicode_escape(s) - local n1 = tonumber( s:sub(1, 4), 16 ) - local n2 = tonumber( s:sub(7, 10), 16 ) - -- Surrogate pair? - if n2 then - return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) - else - return codepoint_to_utf8(n1) - end -end - - -local function parse_string(str, i) - local res = "" - local j = i + 1 - local k = j - - while j <= #str do - local x = str:byte(j) - - if x < 32 then - decode_error(str, j, "control character in string") - - elseif x == 92 then -- `\`: Escape - res = res .. str:sub(k, j - 1) - j = j + 1 - local c = str:sub(j, j) - if c == "u" then - local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) - or str:match("^%x%x%x%x", j + 1) - or decode_error(str, j - 1, "invalid unicode escape in string") - res = res .. parse_unicode_escape(hex) - j = j + #hex - else - if not escape_chars[c] then - decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") - end - res = res .. escape_char_map_inv[c] - end - k = j + 1 - - elseif x == 34 then -- `"`: End of string - res = res .. str:sub(k, j - 1) - return res, j + 1 - end - - j = j + 1 - end - - decode_error(str, i, "expected closing quote for string") -end - - -local function parse_number(str, i) - local x = next_char(str, i, delim_chars) - local s = str:sub(i, x - 1) - local n = tonumber(s) - if not n then - decode_error(str, i, "invalid number '" .. s .. "'") - end - return n, x -end - - -local function parse_literal(str, i) - local x = next_char(str, i, delim_chars) - local word = str:sub(i, x - 1) - if not literals[word] then - decode_error(str, i, "invalid literal '" .. word .. "'") - end - return literal_map[word], x -end - - -local function parse_array(str, i) - local res = {} - local n = 1 - i = i + 1 - while 1 do - local x - i = next_char(str, i, space_chars, true) - -- Empty / end of array? - if str:sub(i, i) == "]" then - i = i + 1 - break - end - -- Read token - x, i = parse(str, i) - res[n] = x - n = n + 1 - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "]" then break end - if chr ~= "," then decode_error(str, i, "expected ']' or ','") end - end - return res, i -end - - -local function parse_object(str, i) - local res = {} - i = i + 1 - while 1 do - local key, val - i = next_char(str, i, space_chars, true) - -- Empty / end of object? - if str:sub(i, i) == "}" then - i = i + 1 - break - end - -- Read key - if str:sub(i, i) ~= '"' then - decode_error(str, i, "expected string for key") - end - key, i = parse(str, i) - -- Read ':' delimiter - i = next_char(str, i, space_chars, true) - if str:sub(i, i) ~= ":" then - decode_error(str, i, "expected ':' after key") - end - i = next_char(str, i + 1, space_chars, true) - -- Read value - val, i = parse(str, i) - -- Set - res[key] = val - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "}" then break end - if chr ~= "," then decode_error(str, i, "expected '}' or ','") end - end - return res, i -end - - -local char_func_map = { - [ '"' ] = parse_string, - [ "0" ] = parse_number, - [ "1" ] = parse_number, - [ "2" ] = parse_number, - [ "3" ] = parse_number, - [ "4" ] = parse_number, - [ "5" ] = parse_number, - [ "6" ] = parse_number, - [ "7" ] = parse_number, - [ "8" ] = parse_number, - [ "9" ] = parse_number, - [ "-" ] = parse_number, - [ "t" ] = parse_literal, - [ "f" ] = parse_literal, - [ "n" ] = parse_literal, - [ "[" ] = parse_array, - [ "{" ] = parse_object, -} - - -parse = function(str, idx) - local chr = str:sub(idx, idx) - local f = char_func_map[chr] - if f then - return f(str, idx) - end - decode_error(str, idx, "unexpected character '" .. chr .. "'") -end - - -function json.decode(str) - if type(str) ~= "string" then - error("expected argument of type string, got " .. type(str)) - end - local res, idx = parse(str, next_char(str, 1, space_chars, true)) - idx = next_char(str, idx, space_chars, true) - if idx <= #str then - decode_error(str, idx, "trailing garbage") - end - return res -end - - -return json diff --git a/prt/client-lua/utils/time.lua b/prt/client-lua/utils/time.lua index 0997ffb9d..418bedc1a 100644 --- a/prt/client-lua/utils/time.lua +++ b/prt/client-lua/utils/time.lua @@ -1,4 +1,25 @@ +-- Every polling loop in the harness and the Lua client sleeps through +-- here, so this is the one choke point that can turn a hang into a +-- failure: a dead node otherwise leaves the driving scenario spinning +-- forever (it burned two hours on 2026-07-09). The deadline arms at +-- module load (scenario start) and errors loudly when crossed; +-- SCENARIO_DEADLINE_SECS overrides, 0 disables. +local scenario_start = os.time() +local deadline = tonumber(os.getenv("SCENARIO_DEADLINE_SECS")) or 3600 + +local function check_deadline() + if deadline > 0 then + local elapsed = os.time() - scenario_start + if elapsed > deadline then + error(string.format( + "scenario deadline exceeded: %ds elapsed (limit %ds; set SCENARIO_DEADLINE_SECS to adjust, 0 to disable)", + elapsed, deadline)) + end + end +end + local function sleep(seconds) + check_deadline() local ok, how, code = os.execute("exec sleep " .. tonumber(seconds)) if not ok and how == "signal" and code == 2 then -- 2 == SIGINT os.exit(130, true) @@ -6,6 +27,7 @@ local function sleep(seconds) end local function sleep_ms(ms) + check_deadline() local ok, how, code = os.execute("exec sleep " .. tonumber(ms / 1000) .. "s") if not ok and how == "signal" and code == 2 then -- 2 == SIGINT os.exit(130, true) diff --git a/prt/tests/common/blockchain/constants.lua b/prt/tests/common/blockchain/constants.lua index 1460505aa..70b8c6610 100644 --- a/prt/tests/common/blockchain/constants.lua +++ b/prt/tests/common/blockchain/constants.lua @@ -1,6 +1,13 @@ -- contains default 40 accounts of anvil test node +-- TEST_INSTANCE isolates parallel runs: it is the anvil port, and the +-- harness suffixes every working-directory singleton with it +-- (_state-, dave-.log, anvil-.log, _oracle-). Unset +-- means the historical defaults. +local port = os.getenv("TEST_INSTANCE") or "8545" + local constants = { - endpoint = "http://127.0.0.1:8545", + endpoint = "http://127.0.0.1:" .. port, + port = port, hero_address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", pks = { "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", diff --git a/prt/tests/common/blockchain/node.lua b/prt/tests/common/blockchain/node.lua index e00ac8eee..239e37934 100644 --- a/prt/tests/common/blockchain/node.lua +++ b/prt/tests/common/blockchain/node.lua @@ -4,11 +4,30 @@ local helper = require "utils.helper" local slots_in_an_epoch = 1 local default_account_number = 40 +local blockchain_data = require "blockchain.constants" +local instance = os.getenv("TEST_INSTANCE") +local anvil_log = instance and ("anvil-" .. instance .. ".log") or "anvil.log" + +-- The anvil port must be free before spawning: macOS port reuse lets a +-- stray listener coexist with ours and silently absorb traffic, +-- turning runs nondeterministic instead of failing. +local function assert_port_free(port) + local connected = os.execute( + string.format("bash -c 'exec 3<>/dev/tcp/127.0.0.1/%s' 2>/dev/null", port) + ) + assert(not connected, string.format( + "something is already listening on 127.0.0.1:%s; kill it or pick another TEST_INSTANCE", + port)) +end + -- spawn an anvil node with 40 accounts, auto-mine, and finalize block at height N-2 local function start_blockchain(anvil_load_path, anvil_dump_path) + assert_port_free(blockchain_data.port) print(string.format("Starting blockchain with %d accounts...", default_account_number)) local anvil_args = { + "--port", + blockchain_data.port, "--slots-in-an-epoch", slots_in_an_epoch, "-a", @@ -27,8 +46,9 @@ local function start_blockchain(anvil_load_path, anvil_dump_path) end local cmd = string.format( - [[ echo $$ ; exec anvil %s > anvil.log 2>&1 ]], - table.concat(anvil_args, " ") + [[ echo $$ ; exec anvil %s > %s 2>&1 ]], + table.concat(anvil_args, " "), + anvil_log ) local reader = io.popen(cmd) @@ -48,7 +68,6 @@ local function start_blockchain(anvil_load_path, anvil_dump_path) end local function capture_blockchain_data() - local blockchain_data = require "blockchain.constants" return blockchain_data.pks, blockchain_data.endpoint end diff --git a/prt/tests/common/runners/helpers/fake_commitment.lua b/prt/tests/common/runners/helpers/fake_commitment.lua deleted file mode 100644 index 8f660efb1..000000000 --- a/prt/tests/common/runners/helpers/fake_commitment.lua +++ /dev/null @@ -1,155 +0,0 @@ -local consts = require "computation.constants" -local MerkleBuilder = require "cryptography.merkle_builder" -local Hash = require "cryptography.hash" -local new_scoped_require = require "test_utils.scoped_require" - -local FakeCommitmentBuilder = {} -FakeCommitmentBuilder.__index = FakeCommitmentBuilder - -local function build_zero_uarch() - local builder = MerkleBuilder:new() - - -- all uarch states are zero including the reset state - builder:add(Hash.zero, consts.uarch_span_to_barch + 1) - - return builder:build() -end - -local function shallow_copy(orig) - local copy = {} - for orig_key, orig_value in next, orig, nil do - copy[orig_key] = orig_value - end - setmetatable(copy, getmetatable(orig)) - return copy -end - -local uarch_zero = build_zero_uarch() - -local function get_fake_hash(log2_stride) - if log2_stride == 0 then - return uarch_zero - else - return Hash.zero - end -end - -local function update_scope_of_hashes(leafs) - -- the leafs are from foreign scope, thus we need to manually call the `Hash` in our scope - -- to have the internal states working - for i = 1, #leafs do - -- update the Hash state from another scope - local l = leafs[i].hash - if l.digest then - -- l is Hash type - local h = Hash:from_digest(l.digest) - leafs[i].hash = h - elseif l.leafs then - -- l is MerkleTree type - update_scope_of_hashes(l.leafs) - end - end -end - -local function rebuild_nested_trees(leafs) - for i = 1, #leafs do - -- if a leaf is also a tree, rebuild it to properly update `Hash` internal states - -- i.e. the relationship between parents and children - local l = leafs[i].hash - if l.leafs then - local builder = MerkleBuilder:new() - builder.leafs = l.leafs - leafs[i].hash = builder:build() - end - end -end - -local function build_commitment(cached_commitments, machine_path, base_cycle, level, log2_stride, - log2_stride_count, inputs, snapshot_dir) - -- the honest commitment builder should be operated in an isolated env - -- to avoid side effects to the strategy behavior - - if not cached_commitments[level] then - cached_commitments[level] = {} - elseif cached_commitments[level][base_cycle] then - return cached_commitments[level][base_cycle] - end - - local c = coroutine.create(function() - local scoped_require = new_scoped_require(_ENV) - local CommitmentBuilder = scoped_require "computation.commitment" - - local builder = CommitmentBuilder:new(machine_path, inputs, nil, snapshot_dir) - local commitment = builder:build(base_cycle, level, log2_stride, log2_stride_count, snapshot_dir) - coroutine.yield(commitment) - end) - - local success, ret = coroutine.resume(c) - if not success then - error(string.format("commitment coroutine fail to resume with error: %s", ret)) - else - cached_commitments[level][base_cycle] = ret - return ret - end -end - -local function build_fake_commitment(commitment, fake_index, log2_stride) - local fake_builder = MerkleBuilder:new() - fake_builder.leafs = shallow_copy(commitment.leafs) - - local fake_hash = get_fake_hash(log2_stride) - local leaf_index = math.max(#commitment.leafs - fake_index + 1, 1) - for i = leaf_index, #commitment.leafs do - local old_leaf = fake_builder.leafs[i] - fake_builder.leafs[i] = { hash = fake_hash, accumulated_count = old_leaf.accumulated_count } - end - - update_scope_of_hashes(fake_builder.leafs) - rebuild_nested_trees(fake_builder.leafs) - - local implicit_hash = Hash:from_digest(commitment.implicit_hash.digest) - return fake_builder:build(implicit_hash) -end - -function FakeCommitmentBuilder:new(machine_path, root_commitment, snapshot_dir) - -- receive honest root commitment from main process - local commitments = { [0] = { [0] = root_commitment } } - - local c = { - fake_index = false, - machine_path = machine_path, - snapshot_dir = snapshot_dir, - fake_commitments = {}, - commitments = commitments - } - setmetatable(c, self) - return c -end - -function FakeCommitmentBuilder:build(base_cycle, level, log2_stride, log2_stride_count, inputs) - -- function caller should set `self.fake_index` properly before calling this function - -- the fake commitments are not guaranteed to be unique if there are not many leafs (short computation) - -- `self.fake_index` is reset and the end of a successful call to ensure the next caller must set it again. - assert(self.fake_index) - if not self.fake_commitments[level] then - self.fake_commitments[level] = {} - end - if not self.fake_commitments[level][base_cycle] then - self.fake_commitments[level][base_cycle] = {} - end - if self.fake_commitments[level][base_cycle][self.fake_index] then - return self.fake_commitments[level][base_cycle][self.fake_index] - end - - local commitment = build_commitment(self.commitments, self.machine_path, base_cycle, level, - log2_stride, - log2_stride_count, - inputs, self.snapshot_dir) - print("honest commitment", commitment) - local fake_commitment = build_fake_commitment(commitment, self.fake_index, log2_stride) - - self.fake_commitments[level][base_cycle][self.fake_index] = fake_commitment - return fake_commitment -end - -return FakeCommitmentBuilder diff --git a/prt/tests/common/runners/hero_runner.lua b/prt/tests/common/runners/hero_runner.lua deleted file mode 100755 index 3b8e232df..000000000 --- a/prt/tests/common/runners/hero_runner.lua +++ /dev/null @@ -1,34 +0,0 @@ --- Required Modules -local blockchain_consts = require "blockchain.constants" -local CommitmentBuilder = require "computation.commitment" -local HonestStrategy = require "player.strategy" -local Sender = require "player.sender" -local Player = require "player.player" - -local function hero_runner(player_id, machine_path, root_commitment, root_tournament, extra_data, inputs) - local hook - - if extra_data then - print("extra data is enabled") - hook = require "doom_showcase.hook" - else - hook = false - end - - local strategy = HonestStrategy:new( - CommitmentBuilder:new(machine_path, inputs, root_commitment), - inputs, - machine_path, - Sender:new(blockchain_consts.pks[player_id], player_id, blockchain_consts.endpoint) - ) - local react = Player.new( - root_tournament, - strategy, - blockchain_consts.endpoint, - hook - ) - - return react -end - -return hero_runner diff --git a/prt/tests/common/runners/idle_runner.lua b/prt/tests/common/runners/idle_runner.lua deleted file mode 100755 index 4edb6ae66..000000000 --- a/prt/tests/common/runners/idle_runner.lua +++ /dev/null @@ -1,23 +0,0 @@ --- Required Modules -local blockchain_consts = require "blockchain.constants" -local DummyCommitment = require "runners.helpers.dummy_commitment" -local IdleStrategy = require "runners.helpers.idle_strategy" -local Sender = require "player.sender" -local Player = require "player.player" - -local function idle_runner(player_id, machine_path, root_tournament) - local strategy = IdleStrategy:new( - DummyCommitment:new(machine_path), - Sender:new(blockchain_consts.pks[player_id], player_id, blockchain_consts.endpoint) - ) - local react = Player.new( - root_tournament, - strategy, - blockchain_consts.endpoint, - false - ) - - return react -end - -return idle_runner diff --git a/prt/tests/common/runners/rust_hero_runner.lua b/prt/tests/common/runners/rust_hero_runner.lua deleted file mode 100644 index fa7a46344..000000000 --- a/prt/tests/common/runners/rust_hero_runner.lua +++ /dev/null @@ -1,115 +0,0 @@ --- Required Modules -local blockchain_consts = require "blockchain.constants" -local helper = require "utils.helper" - -local COMPUTE_BIN = "../../../target/debug/cartesi-prt-compute" - -local function set_int_handler(reader, pid) - local signal = require("posix.signal") - signal.signal(signal.SIGINT, function() - helper.stop_pid(reader, pid) - os.exit(1) - end) -end - -local function get_hero_nonce() - local hero_nonce_cmd = string.format("cast nonce %s --rpc-url %s", - blockchain_consts.hero_address, blockchain_consts.endpoint) - local process = io.popen(hero_nonce_cmd) -- Execute the command - assert(process, "Failed to open process for hero nonce") -- Check if process is nil - local output = process:read("*a") -- Read all output - local success, _, code = process:close() -- Close the process - assert(success, string.format("Hero nonce command failed:\n%d", code)) - - -- Convert the output to an integer - local nonce = tonumber(output:match("%d+")) -- Extract the first number from the output - return nonce -end - --- The Rust Compute reacts once and exits, the coroutine periodically spawn a new process until the tournament ends -local function create_react_once_runner(player_id, machine_path, root_tournament) - local rust_compute_cmd = string.format( - [[echo $$ ; exec env WEB3_PRIVATE_KEY='%s' MACHINE_PATH='%s' ROOT_TOURNAMENT='%s' RUST_LOG='info' %s 2>&1 | tee -a honest.log]], - blockchain_consts.pks[1], machine_path, root_tournament, COMPUTE_BIN) - - return coroutine.create(function() - -- Prepare temp directory for the Rust compute node to exchange information - local temp_dir = os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "/tmp" - assert(temp_dir, "No temp directory to receive notification from Rust node") - local tournament_dir = temp_dir .. "/" .. string.upper(root_tournament) - helper.mkdir_p(tournament_dir) - local finished = tournament_dir .. "/finished" - helper.remove_file(finished) - helper.remove_file("honest.log") - print("Monitoring finished temp file: " .. finished) - - while true do - local tx_count = get_hero_nonce() - local reader = assert(io.popen(rust_compute_cmd)) - local hero_pid = tonumber(reader:read()) - - while true do - local output = reader:read() - if not output then break end - helper.log_color(player_id, output) - io.flush() - end - - local success, _, code = reader:close() - assert(success, string.format("Rust compute command failed to close:\n%d", code)) - - if helper.exists(finished) then - print("Rust compute finished") - break - end - - local idle = tx_count == get_hero_nonce() - coroutine.yield({ idle = idle, finished = false }) - end - end) -end - --- The Rust Compute reacts in a loop until the tournament ends, the coroutine pulls its state periodically until the process ends -local function create_runner(player_id, machine_path, root_tournament) - local hero_react_interval = 3 - local rust_compute_cmd = string.format( - [[echo $$ ; exec env WEB3_PRIVATE_KEY='%s' INTERVAL='%d' MACHINE_PATH='%s' ROOT_TOURNAMENT='%s' %s 2>&1 | tee honest.log]], - blockchain_consts.pks[1], hero_react_interval, machine_path, root_tournament, COMPUTE_BIN) - - return coroutine.create(function() - local start_time = os.time() - local tx_count = get_hero_nonce() - local reader = io.popen(rust_compute_cmd) - assert(reader, "Failed to open process for Rust compute: " .. rust_compute_cmd) - local hero_pid = tonumber(reader:read()) - - set_int_handler(reader, hero_pid) - - print(string.format("Hero running with pid %d", hero_pid)) - local prev_msg = false - - while true do - if prev_msg then - helper.log_color(player_id, prev_msg) - prev_msg = false - end - - prev_msg = helper.log_to_ts(player_id, reader, start_time + hero_react_interval) - - start_time = os.time() - if not helper.is_pid_alive(hero_pid) then - break - end - - local new_tx_count = get_hero_nonce() - local idle = tx_count == new_tx_count - tx_count = new_tx_count - coroutine.yield({ idle = idle, finished = false }) - end - - local success, _, code = reader:close() - assert(success, string.format("Rust compute command failed to close:\n%d", code)) - end) -end - -return { create_runner = create_runner, create_react_once_runner = create_react_once_runner } diff --git a/prt/tests/common/runners/sybil_runner.lua b/prt/tests/common/runners/sybil_runner.lua index 64638bea5..3524fc3e7 100755 --- a/prt/tests/common/runners/sybil_runner.lua +++ b/prt/tests/common/runners/sybil_runner.lua @@ -22,9 +22,25 @@ local function sybil_player(root_tournament, strategy, blockchain_endpoint) end) end +-- Sybils sign with their own accounts, auto-allocated from 2 up: +-- account 1 is the honest node's, and the old shared default wedged +-- on nonces the moment two sybils sent concurrently (found building +-- multi_sybil; every serial scenario had silently gotten away with +-- it). Pass player_id or config.pk to override deliberately - but +-- never the node's own account. +local next_player_id = 2 + local function sybil_runner(commitment_builder, machine_path, root_tournament, inputs, player_id, config) config = config or {} - player_id = player_id or 1 + if not player_id then + player_id = next_player_id + next_player_id = next_player_id + 1 + end + assert( + config.pk or player_id ~= 1, + "player_id 1 is the honest node's account; sybils sign with their own" + ) + assert(blockchain_consts.pks[player_id], "no test account for player_id " .. player_id) local pk = config.pk or blockchain_consts.pks[player_id] local endpoint = config.endpoint or blockchain_consts.endpoint diff --git a/prt/tests/common/test_utils/example.lua b/prt/tests/common/test_utils/example.lua deleted file mode 100644 index ba19b2d07..000000000 --- a/prt/tests/common/test_utils/example.lua +++ /dev/null @@ -1,137 +0,0 @@ -require "setup_path" -assert(#package.loaded == 1) - --- We're currenly on scope "zero" -local env0, const0 = _ENV, require "blockchain.constants" - --- Scope/sandbox creator -local new_scoped_require = require "test_utils.scoped_require" - --- --- Create scope/sandbox 1 -local scoped_require1 = new_scoped_require(_ENV) - --- In scope/sandbox 1, load "utils.test" -local env1, const1 = scoped_require1 "test_utils.test" - --- Check that in scope 1, both _ENV and "test_utils.scoped_require" are different -assert(env0 ~= env1) -assert(const0 ~= const1) - - --- --- Create scope/sandbox 2 -local scoped_require2 = new_scoped_require(_ENV) - --- In sandbox 2, load "utils.test" -local env2, const2 = scoped_require2 "test_utils.test" - --- Check that in scope 2, both _ENV and "test_utils.scoped_require" are different -assert(env1 ~= env2) -assert(const1 ~= const2) - - --- --- Applying it to players --- - --- Shared setup -local blockchain_consts = require "blockchain.constants" -local tournament_address = "..." -local machine_path = "..." -local hook = false - --- Create honest player 0 in its own scope/sandbox -local _debug_p0 -- debug only -local player0 -do - local player_id = 0 - local wallet = { pk = blockchain_consts.pks[player_id], player_id = player_id } - - local scoped_require = new_scoped_require(_ENV) -- create sandbox - local Player = scoped_require "player.player" - local react = Player.new( - tournament_address, - wallet, - machine_path, - blockchain_consts.endpoint, - hook - ) - - player0 = react - _debug_p0 = Player -end - --- Create honest player 1 in its own scope/sandbox -local _debug_p1 -- debug only -local player1 -do - local player_id = 1 - local wallet = { pk = blockchain_consts.pks[player_id], player_id = player_id } - - local scoped_require = new_scoped_require(_ENV) -- create sandbox - local Player = scoped_require "player.player" - local react = Player.new( - tournament_address, - wallet, - machine_path, - blockchain_consts.endpoint, - hook - ) - - player1 = react - _debug_p1 = Player -end - -assert(_debug_p0 ~= _debug_p1) - --- now we have to players: player0 and player1. --- these are actually coroutines!! --- let's use them. - -local function run_player(player, idx) - local ok, log = coroutine.resume(player) - - if not ok then - print(string.format("player %d died", idx)) - return false - elseif coroutine.status(player0) == "dead" then - print(string.format("player %d has finished", idx)) - return false - else - return true, log - end -end - -local function run(players) - local finished = false - - repeat - finished = true - local idle = true - - for i, player in ipairs(players) do - if not player then - goto continue - end - - local ok, log = run_player(player, i) - - if ok then - finished = finished and log.finished - idle = idle and log.idle - end - - ::continue:: - end - - if idle then - -- all players are idle - -- evm advance time - end - - time.sleep(5) -- I'm thinking, can we remove this and just rely on advances? - until finished -end - --- run { player0, player1 } diff --git a/prt/tests/common/test_utils/test.lua b/prt/tests/common/test_utils/test.lua deleted file mode 100644 index 58e8f8b20..000000000 --- a/prt/tests/common/test_utils/test.lua +++ /dev/null @@ -1,4 +0,0 @@ -assert(#package.loaded == 0) -require "setup_path" -local const = assert(require "blockchain.constants") -return _ENV, const diff --git a/prt/tests/rollups/.gitignore b/prt/tests/rollups/.gitignore index b15ebffba..e59344de3 100644 --- a/prt/tests/rollups/.gitignore +++ b/prt/tests/rollups/.gitignore @@ -1,3 +1,6 @@ *.log -_state/ +*.log.prev +_state*/ +_oracle*/ +_battery/ anvil*.json diff --git a/prt/tests/rollups/README.md b/prt/tests/rollups/README.md index 31548f3e6..5de110294 100644 --- a/prt/tests/rollups/README.md +++ b/prt/tests/rollups/README.md @@ -1,16 +1,30 @@ -# PRT Rollups test +# PRT rollups tests -This tests the rollups Rust node. -The node test will be conducted with a Lua orchestrator script spawning an honest rollups node in the background to advance the rollups states and to defend the application. -The Lua orchestrator script also spawns multiple [dishonest nodes](../compute/README.md) trying to tamper with the rollups states. +End-to-end tests for the rollups Rust node. A Lua orchestrator spawns an +honest node in the background to advance the rollups state and defend the +application, plus dishonest sybil players (built from the Lua client, see +`prt/tests/common/runners/`) that tamper with commitments and must lose. -Remember to either clone the repository with the flag `--recurse-submodules`, or run `git submodule update --recursive --init` after cloning. -You need a docker installation to run the Dave Lua node. +How the harness works, what the scenarios cover, and how to add one: +see [docs/test-harness.md](../../../docs/test-harness.md). -## Run echo test +## Setup -A simple [echo program](./program/echo/) is provided to test the rollups. +Clone with `--recurse-submodules`, or run +`git submodule update --recursive --init` after cloning, then follow the +setup in the [root README](../../../README.md). Building the honeypot +machine image requires docker. + +## Running + +From the repository root: ```bash -just test-echo +just test-rollups-echo # echo program, simple scenario +just test-rollups-honeypot # full honeypot scenario suite +just test-rollups-honeypot-case gc_match # one scenario +just view-rollups-logs # tail the node's dave.log ``` + +Machine programs live in [test/programs](../../../test/programs/); the +scenario scripts live in [test_cases](./test_cases/). diff --git a/prt/tests/rollups/battery.sh b/prt/tests/rollups/battery.sh new file mode 100755 index 000000000..73e90cb9e --- /dev/null +++ b/prt/tests/rollups/battery.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# The full e2e battery, parallel: one TEST_INSTANCE (anvil port + +# suffixed working-dir singletons) per scenario, LANES at a time, so +# wall clock is the max of the set instead of the sum. Chaos runs at +# a fixed seed: the battery is a regression net, seed exploration is +# a separate exercise. Instance dirs and logs are left in place for +# forensics; sweep them once the results are read. +# +# LANES=5 BASE_PORT=8601 ./battery.sh +# +# Results land in _battery/results.txt as "