22pragma solidity ^ 0.8.28 ;
33
44import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol " ;
5+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol " ;
6+
7+ import {LibSort} from "solady/src/utils/LibSort.sol " ;
58
69import {BlockHeader, BlockHeaderData} from "./libs/BlockHeader.sol " ;
7- import {BlocksStorage } from "./libs/BlocksStorage .sol " ;
8- import {TargetsStorage} from " ./libs/targets/TargetsStorage.sol " ;
9- import {TargetsHelper } from "./libs/targets/TargetsHelper .sol " ;
10+ import {TargetsHelper } from "./libs/TargetsHelper .sol " ;
11+
12+ import {ISPVContract } from "./interfaces/ISPVContract .sol " ;
1013
11- contract SPVContract is Initializable {
14+ contract SPVContract is ISPVContract , Initializable {
15+ using EnumerableSet for EnumerableSet.Bytes32Set;
1216 using BlockHeader for bytes ;
13- using BlocksStorage for BlocksStorage.BlocksData;
14- using TargetsStorage for TargetsStorage.TargetsData;
17+ using TargetsHelper for bytes32 ;
18+
19+ uint8 public constant MEDIAN_PAST_BLOCKS = 11 ;
1520
1621 bytes32 public constant SPV_CONTRACT_STORAGE_SLOT =
1722 keccak256 ("spv.contract.spv.contract.storage " );
1823
19- error PrevBlockDoesNotExist (bytes32 prevBlockHash );
20- error BlockAlreadyExists (bytes32 blockHash );
21-
22- error InvalidTarget (bytes32 blockTarget , bytes32 networkTarget );
23- error InvalidBlockHash (bytes32 actualBlockHash , bytes32 blockTarget );
24- error InvalidBlockTime (uint32 blockTime , uint32 medianTime );
25-
26- event BlockHeaderAdded (uint256 indexed blockHeight , bytes32 indexed blockHash );
27-
2824 struct SPVContractStorage {
29- BlocksStorage.BlocksData blocksData;
30- TargetsStorage.TargetsData targets;
31- uint256 pendingTargetHeightCount;
25+ mapping (bytes32 => BlockData) blocksData;
26+ mapping (uint256 => bytes32 ) blocksHeightToBlockHash;
27+ bytes32 mainchainHead;
28+ uint256 lastEpochCumulativeWork;
3229 }
3330
34- function __SPVContract_init (
35- uint256 pendingBlockCount_ ,
36- uint256 pendingTargetHeightCount_
37- ) external initializer {
38- SPVContractStorage storage $ = _getSPVContractStorage ();
39-
40- $.blocksData.initialize (pendingBlockCount_);
41- $.targets.initialize ();
42-
43- $.pendingTargetHeightCount = pendingTargetHeightCount_;
31+ function __SPVContract_init () external initializer {
32+ BlockHeaderData memory genesisBlockHeader_ = BlockHeaderData ({
33+ version: 1 ,
34+ prevBlockHash: bytes32 (0 ),
35+ merkleRoot: 0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b ,
36+ time: 1231006505 ,
37+ bits: 0x1d00ffff ,
38+ nonce: 2083236893
39+ });
40+ bytes32 genesisBlockHash_ = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f ;
41+
42+ _addBlock (genesisBlockHeader_, genesisBlockHash_, 0 );
4443 }
4544
46- function _getSPVContractStorage () internal pure returns (SPVContractStorage storage _spvs ) {
45+ function _getSPVContractStorage () private pure returns (SPVContractStorage storage _spvs ) {
4746 bytes32 slot_ = SPV_CONTRACT_STORAGE_SLOT;
4847
4948 assembly {
@@ -57,95 +56,180 @@ contract SPVContract is Initializable {
5756 (BlockHeaderData memory blockHeader_ , bytes32 blockHash_ ) = blockHeaderRaw_
5857 .parseBlockHeaderData ();
5958
60- require (! $.blocksData. blockExists (blockHash_), BlockAlreadyExists (blockHash_));
59+ require (! blockExists (blockHash_), BlockAlreadyExists (blockHash_));
6160 require (
62- $.blocksData. blockExists (blockHeader_.prevBlockHash),
61+ blockExists (blockHeader_.prevBlockHash),
6362 PrevBlockDoesNotExist (blockHeader_.prevBlockHash)
6463 );
6564
66- uint256 blockHeight_ = $.blocksData.getBlockHeight (blockHeader_.prevBlockHash) + 1 ;
65+ uint256 blockHeight_ = $.blocksData[blockHeader_.prevBlockHash].blockHeight + 1 ;
66+ bytes32 currentTarget_ = getBlockTarget (blockHeader_.prevBlockHash);
6767
68- _tryConfirmPendingTarget (blockHeight_);
68+ if (TargetsHelper.isTargetAdjustmentBlock (blockHeight_)) {
69+ $.lastEpochCumulativeWork += TargetsHelper.countEpochCumulativeWork (currentTarget_);
6970
70- bytes32 target_ = _getRequiredTarget (blockHeight_, blockHeader_.prevBlockHash);
71-
72- _validateBlockRules (blockHeader_, blockHash_, target_);
71+ uint256 passedTime_ = blockHeader_.time -
72+ getBlockTimeByBlockHeight (
73+ blockHeight_ - TargetsHelper.DIFFICULTY_ADJUSTMENT_INTERVAL
74+ );
75+ currentTarget_ = TargetsHelper.countNewRoundedTarget (currentTarget_, passedTime_);
76+ }
7377
74- $.blocksData. addBlock (
78+ _validateBlockRules (
7579 blockHeader_,
7680 blockHash_,
77- blockHeight_ ,
78- TargetsStorage. getBlockWork (target_ )
81+ currentTarget_ ,
82+ getMedianTime (blockHeader_.prevBlockHash )
7983 );
8084
81- if (TargetsStorage.isTargetAdjustmentBlock (blockHeight_)) {
82- $.targets.updatePendingTarget (
83- blockHeight_,
84- blockHash_,
85- $.blocksData.getBlockTimeByBlockHeight (
86- blockHeight_ - TargetsStorage.DIFFICULTY_ADJSTMENT_INTERVAL
87- ),
88- blockHeader_.time
89- );
90- }
85+ _addBlock (blockHeader_, blockHash_, blockHeight_);
9186
9287 emit BlockHeaderAdded (blockHeight_, blockHash_);
9388 }
9489
95- function _getRequiredTarget (
96- uint256 blockHeight_ ,
97- bytes32 prevBlockHash_
98- ) internal view returns (bytes32 target_ ) {
90+ function getMainchainHead () external view returns (bytes32 ) {
91+ return _getSPVContractStorage ().mainchainHead;
92+ }
93+
94+ function getBlockHeight (bytes32 blockHash_ ) external view returns (uint256 ) {
95+ return _getSPVContractStorage ().blocksData[blockHash_].blockHeight;
96+ }
97+
98+ function getBlockHash (uint256 blockHeight_ ) external view returns (bytes32 ) {
99+ return _getSPVContractStorage ().blocksHeightToBlockHash[blockHeight_];
100+ }
101+
102+ function getBlockTimeByBlockHeight (uint256 blockHeight_ ) public view returns (uint32 ) {
99103 SPVContractStorage storage $ = _getSPVContractStorage ();
100104
101- uint256 blockTargetEpoch_ = TargetsStorage. countTargetEpoch ( blockHeight_) ;
102- bool isPendingTargetRequired_ = blockTargetEpoch_ == $.targets. getPendingEpoch ();
105+ return $.blocksData[$.blocksHeightToBlockHash[ blockHeight_]].header.time ;
106+ }
103107
104- if (isPendingTargetRequired_) {
105- uint256 epochBlockNumber_ = TargetsStorage.getEpochBlockNumber (blockHeight_);
108+ function getBlockTarget (bytes32 blockHash_ ) public view returns (bytes32 ) {
109+ return
110+ TargetsHelper.bitsToTarget (
111+ _getSPVContractStorage ().blocksData[blockHash_].header.bits
112+ );
113+ }
106114
107- for (uint256 i = 0 ; i < epochBlockNumber_ - 1 ; ++ i) {
108- prevBlockHash_ = $.blocksData.getPrevBlockHash (prevBlockHash_);
109- }
115+ function blockExists (bytes32 blockHash_ ) public view returns (bool ) {
116+ return _getSPVContractStorage ().blocksData[blockHash_].header.time > 0 ;
117+ }
118+
119+ function getMainchainBlockHeight () public view returns (uint256 ) {
120+ SPVContractStorage storage $ = _getSPVContractStorage ();
121+
122+ return $.blocksData[$.mainchainHead].blockHeight;
123+ }
124+
125+ function getMedianTime (bytes32 toBlockHash_ ) public view returns (uint32 ) {
126+ SPVContractStorage storage $ = _getSPVContractStorage ();
127+ uint256 blockHeight_ = $.blocksData[toBlockHash_].blockHeight;
128+
129+ if (blockHeight_ <= MEDIAN_PAST_BLOCKS || getMainchainBlockHeight () < MEDIAN_PAST_BLOCKS) {
130+ return 0 ;
131+ }
132+
133+ uint256 [] memory blocksTime_ = new uint256 [](MEDIAN_PAST_BLOCKS);
134+ uint256 blocksTimeIndex_ = MEDIAN_PAST_BLOCKS;
135+
136+ for (uint256 i = blockHeight_ - MEDIAN_PAST_BLOCKS; i < blockHeight_; ++ i) {
137+ blocksTime_[-- blocksTimeIndex_] = $.blocksData[toBlockHash_].header.time;
110138
111- target_ = $.targets.getPendingTarget (prevBlockHash_);
112- } else {
113- target_ = $.targets.getTarget (blockTargetEpoch_);
139+ toBlockHash_ = $.blocksData[toBlockHash_].header.prevBlockHash;
114140 }
115141
116- assert (target_ > 0 );
142+ LibSort.insertionSort (blocksTime_);
143+
144+ return uint32 (blocksTime_[MEDIAN_PAST_BLOCKS / 2 ]);
145+ }
146+
147+ function isInMainchain (bytes32 blockHash_ ) public view returns (bool ) {
148+ SPVContractStorage storage $ = _getSPVContractStorage ();
149+
150+ return $.blocksHeightToBlockHash[$.blocksData[blockHash_].blockHeight] == blockHash_;
151+ }
152+
153+ function _addBlock (
154+ BlockHeaderData memory blockHeader_ ,
155+ bytes32 blockHash_ ,
156+ uint256 blockHeight_
157+ ) internal {
158+ SPVContractStorage storage $ = _getSPVContractStorage ();
159+
160+ $.blocksData[blockHash_] = BlockData ({header: blockHeader_, blockHeight: blockHeight_});
161+
162+ _updateMainchainHead (blockHeader_, blockHash_, blockHeight_);
117163 }
118164
119- function _tryConfirmPendingTarget (uint256 blockHeight_ ) internal {
165+ function _updateMainchainHead (
166+ BlockHeaderData memory blockHeader_ ,
167+ bytes32 blockHash_ ,
168+ uint256 blockHeight_
169+ ) internal {
120170 SPVContractStorage storage $ = _getSPVContractStorage ();
121171
122- if (! $.targets.hasPendingTarget () || blockHeight_ < $.pendingTargetHeightCount) {
172+ bytes32 mainchainHead = $.mainchainHead;
173+
174+ if (blockHeader_.prevBlockHash == mainchainHead) {
175+ $.mainchainHead = blockHash_;
176+ $.blocksHeightToBlockHash[blockHeight_] = blockHash_;
177+
123178 return ;
124179 }
125180
126- uint256 lastActiveBlockHeight_ = blockHeight_ - $.pendingTargetHeightCount;
181+ uint256 mainchainCumulativeWork_ = _getBlockCumulativeWork (
182+ $.blocksData[mainchainHead].blockHeight,
183+ getBlockTarget (mainchainHead)
184+ );
185+ uint256 newBlockCumulativeWork_ = _getBlockCumulativeWork (
186+ blockHeight_,
187+ getBlockTarget (blockHash_)
188+ );
127189
128- if (TargetsStorage.isTargetAdjustmentBlock (lastActiveBlockHeight_)) {
129- $.targets.confirmPendingTarget (
130- $.blocksData.getBlockHashByBlockHeight (lastActiveBlockHeight_)
131- );
190+ if (newBlockCumulativeWork_ > mainchainCumulativeWork_) {
191+ $.mainchainHead = blockHash_;
192+ $.blocksHeightToBlockHash[blockHeight_] = blockHash_;
193+
194+ bytes32 prevBlockHash_ = blockHeader_.prevBlockHash;
195+ uint256 prevBlockHeight_ = blockHeight_ - 1 ;
196+ while (true ) {
197+ if (
198+ $.blocksHeightToBlockHash[prevBlockHeight_] == prevBlockHash_ ||
199+ prevBlockHash_ == 0
200+ ) {
201+ break ;
202+ }
203+
204+ $.blocksHeightToBlockHash[prevBlockHeight_] = prevBlockHash_;
205+
206+ prevBlockHash_ = $.blocksData[prevBlockHash_].header.prevBlockHash;
207+ prevBlockHeight_ -= 1 ;
208+ }
132209 }
133210 }
134211
212+ function _getBlockCumulativeWork (
213+ uint256 blockHeight_ ,
214+ bytes32 target_
215+ ) internal view returns (uint256 ) {
216+ uint256 currentEpochCumulativeWork_ = target_.countCumulativeWork (
217+ TargetsHelper.getEpochBlockNumber (blockHeight_) + 1
218+ );
219+
220+ return _getSPVContractStorage ().lastEpochCumulativeWork + currentEpochCumulativeWork_;
221+ }
222+
135223 function _validateBlockRules (
136224 BlockHeaderData memory blockHeader_ ,
137225 bytes32 blockHash_ ,
138- bytes32 target_
139- ) internal view {
140- SPVContractStorage storage $ = _getSPVContractStorage ();
141-
226+ bytes32 target_ ,
227+ uint32 medianTime_
228+ ) internal pure {
142229 bytes32 blockTarget_ = TargetsHelper.bitsToTarget (blockHeader_.bits);
143230
144231 require (target_ == blockTarget_, InvalidTarget (blockTarget_, target_));
145232 require (blockHash_ <= blockTarget_, InvalidBlockHash (blockHash_, blockTarget_));
146-
147- uint32 medianTime_ = $.blocksData.getMedianTime (blockHeader_.prevBlockHash);
148-
149233 require (blockHeader_.time > medianTime_, InvalidBlockTime (blockHeader_.time, medianTime_));
150234 }
151235}
0 commit comments