Skip to content

Commit 75e81f9

Browse files
committed
Optimize SPV contract
1 parent 6e4e564 commit 75e81f9

File tree

14 files changed

+222
-1235
lines changed

14 files changed

+222
-1235
lines changed

contracts/SPVContract.sol

Lines changed: 162 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,47 @@
22
pragma solidity ^0.8.28;
33

44
import {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

69
import {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
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
5+
6+
import {BlockHeaderData} from "../libs/BlockHeader.sol";
7+
8+
interface ISPVContract {
9+
error PrevBlockDoesNotExist(bytes32 prevBlockHash);
10+
error InvalidBlocksOrder();
11+
error BlockAlreadyExists(bytes32 blockHash);
12+
13+
error InvalidNewBlockHeight(uint256 currentHeight, uint256 passedHeight);
14+
error InvalidTarget(bytes32 blockTarget, bytes32 networkTarget);
15+
error InvalidBlockHash(bytes32 actualBlockHash, bytes32 blockTarget);
16+
error InvalidBlockTime(uint32 blockTime, uint32 medianTime);
17+
18+
event BlockHeaderAdded(uint256 indexed blockHeight, bytes32 indexed blockHash);
19+
20+
struct BlockData {
21+
BlockHeaderData header;
22+
uint256 blockHeight;
23+
}
24+
}

0 commit comments

Comments
 (0)