-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathSlashIndicator.sol
More file actions
222 lines (199 loc) · 7.61 KB
/
SlashIndicator.sol
File metadata and controls
222 lines (199 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// SPDX-License-Identifier: Apache2.0
pragma solidity 0.8.4;
import "./System.sol";
import "./lib/BytesToTypes.sol";
import "./lib/Memory.sol";
import "./lib/BytesLib.sol";
import "./interface/ISlashIndicator.sol";
import "./interface/IValidatorSet.sol";
import "./interface/ISystemReward.sol";
import "./lib/RLPDecode.sol";
import "./lib/RLPEncode.sol";
/// This contract manages slash/jail operations to validators on Core blockchain
contract SlashIndicator is ISlashIndicator,System{
using RLPDecode for bytes;
using RLPDecode for RLPDecode.RLPItem;
using RLPEncode for bytes;
using RLPEncode for bytes[];
uint256 public constant MISDEMEANOR_THRESHOLD = 50;
uint256 public constant FELONY_THRESHOLD = 150;
uint256 public constant DECREASE_RATE = 4;
uint256 public constant INIT_REWARD_FOR_REPORT_DOUBLE_SIGN = 5e24;
uint32 public constant CHAINID = 1167;
uint256 public constant INIT_FELONY_DEPOSIT = 1e25;
uint256 public constant INIT_FELONY_ROUND = 2;
uint256 public constant INFINITY_ROUND = 0xFFFFFFFFFFFFFFFF;
// State of the contract
address[] public validators;
mapping(address => Indicator) public indicators;
uint256 public previousHeight;
uint256 public misdemeanorThreshold;
uint256 public felonyThreshold;
uint256 public rewardForReportDoubleSign;
uint256 public felonyDeposit;
uint256 public felonyRound;
struct Indicator {
uint256 height;
uint256 count;
bool exist;
}
modifier oncePerBlock() {
require(block.number > previousHeight, "can not slash twice in one block");
_;
previousHeight = block.number;
}
/*********************** events **************************/
event validatorSlashed(address indexed validator);
event indicatorCleaned();
//event paramChange(string key, bytes value);
function init() external onlyNotInit{
misdemeanorThreshold = MISDEMEANOR_THRESHOLD;
felonyThreshold = FELONY_THRESHOLD;
rewardForReportDoubleSign = INIT_REWARD_FOR_REPORT_DOUBLE_SIGN;
felonyDeposit = INIT_FELONY_DEPOSIT;
felonyRound = INIT_FELONY_ROUND;
alreadyInit = true;
}
/*********************** External func ********************************/
/// Slash the validator because of unavailability
/// This method is called by other validators from golang consensus engine.
/// @param validator The consensus address of validator
function slash(address validator) external onlyCoinbase onlyInit oncePerBlock onlyZeroGasPrice{
if (!IValidatorSet(VALIDATOR_CONTRACT_ADDR).isValidator(validator)) {
return;
}
Indicator memory indicator = indicators[validator];
if (indicator.exist) {
indicator.count++;
} else {
indicator.exist = true;
indicator.count = 1;
validators.push(validator);
}
indicator.height = block.number;
if (indicator.count % felonyThreshold == 0) {
indicator.count = 0;
IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator, felonyRound, felonyDeposit);
} else if (indicator.count % misdemeanorThreshold == 0) {
IValidatorSet(VALIDATOR_CONTRACT_ADDR).misdemeanor(validator);
}
indicators[validator] = indicator;
emit validatorSlashed(validator);
}
/// Slash the validator because of double sign
/// This method is called by external verifiers
/// @param header1 A block header submitted by the validator
/// @param header2 Another block header submitted by the validator with same height and parent
function doubleSignSlash(bytes calldata header1, bytes calldata header2) external onlyInit {
RLPDecode.RLPItem[] memory items1 = header1.toRLPItem().toList();
RLPDecode.RLPItem[] memory items2 = header2.toRLPItem().toList();
require(items1[0].toUintStrict() == items2[0].toUintStrict(),"parent of two blocks must be the same");
(bytes32 sigHash1, address validator1) = parseHeader(items1);
(bytes32 sigHash2, address validator2) = parseHeader(items2);
require(sigHash1 != sigHash2, "must be two different blocks");
require(validator1 != address(0x00), "validator is illegal");
require(validator1 == validator2, "must be the same validator");
require(IValidatorSet(VALIDATOR_CONTRACT_ADDR).isValidator(validator1), "not a validator");
IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator1, INFINITY_ROUND, felonyDeposit);
ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportDoubleSign);
}
/// Clean slash record by felonyThreshold/DECREASE_RATE.
/// @dev To prevent validator misbehaving and leaving, do not clean slash record
/// @dev to zero, but decrease by felonyThreshold/DECREASE_RATE.
/// @dev Clean is an effective implement to reorganize "validators" and "indicators".
function clean() external override(ISlashIndicator) onlyCandidate onlyInit{
if(validators.length == 0){
return;
}
uint256 i = 0;
uint256 j = validators.length-1;
for (;i <= j;) {
bool findLeft = false;
bool findRight = false;
for(;i<j;i++){
Indicator memory leftIndicator = indicators[validators[i]];
if(leftIndicator.count > felonyThreshold/DECREASE_RATE){
leftIndicator.count = leftIndicator.count - felonyThreshold/DECREASE_RATE;
indicators[validators[i]] = leftIndicator;
}else{
findLeft = true;
break;
}
}
for(;i<=j;j--){
Indicator memory rightIndicator = indicators[validators[j]];
if(rightIndicator.count > felonyThreshold/DECREASE_RATE){
rightIndicator.count = rightIndicator.count - felonyThreshold/DECREASE_RATE;
indicators[validators[j]] = rightIndicator;
findRight = true;
break;
}else{
delete indicators[validators[j]];
validators.pop();
}
// avoid underflow
if(j==0){
break;
}
}
// swap element in array
if (findLeft && findRight){
delete indicators[validators[i]];
validators[i] = validators[j];
validators.pop();
}
// avoid underflow
if(j==0){
break;
}
// move to next
i++;
j--;
}
emit indicatorCleaned();
}
/*********************** query api ********************************/
/// Get slash indicators of a validator
/// @param validator The validator address to query
function getSlashIndicator(address validator) external view returns (uint256,uint256) {
Indicator memory indicator = indicators[validator];
return (indicator.height, indicator.count);
}
/*********************** Internal Functions **************************/
function parseHeader(RLPDecode.RLPItem[] memory items) internal pure returns (bytes32,address){
bytes memory extra = items[12].toBytes();
bytes memory sig = BytesLib.slice(extra, 32, 65);
bytes[] memory rlpbytes_list = new bytes[](16);
rlpbytes_list[0] = RLPEncode.encodeUint(uint(CHAINID));
for(uint256 i = 0;i < 15;++i){
if(i == 12){
rlpbytes_list[13] = BytesLib.slice(extra, 0, 32).encodeBytes();
} else {
rlpbytes_list[i + 1] = items[i].toRlpBytes();
}
}
bytes memory rlpbytes = rlpbytes_list.encodeList();
bytes32 sigHash = keccak256(rlpbytes);
return (sigHash , ecrecovery(sigHash,sig));
}
function ecrecovery(bytes32 hash, bytes memory sig) internal pure returns (address) {
bytes32 r;
bytes32 s;
uint8 v;
if (sig.length != 65) {
return address(0x0);
}
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := and(mload(add(sig, 65)), 255)
}
if (v < 27) {
v += 27;
}
if (v != 27 && v != 28) {
return address(0x0);
}
return ecrecover(hash, v, r, s);
}
}