Coverage Summary for Class: Federation (co.rsk.peg)
Class |
Class, %
|
Method, %
|
Line, %
|
Federation |
100%
(1/1)
|
5.3%
(1/19)
|
17%
(9/53)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package co.rsk.peg;
20
21 import co.rsk.bitcoinj.core.Address;
22 import co.rsk.bitcoinj.core.BtcECKey;
23 import co.rsk.bitcoinj.core.NetworkParameters;
24 import co.rsk.bitcoinj.script.Script;
25 import co.rsk.bitcoinj.script.ScriptBuilder;
26
27 import java.time.Instant;
28 import java.time.temporal.ChronoUnit;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Objects;
33 import java.util.stream.Collectors;
34
35 /**
36 * Immutable representation of an RSK Federation in the context of
37 * a specific BTC network.
38 *
39 * @author Ariel Mendelzon
40 */
41
42 public class Federation {
43 protected final List<FederationMember> members;
44 protected final Instant creationTime;
45 protected final long creationBlockNumber;
46 protected final NetworkParameters btcParams;
47
48 protected Script redeemScript;
49 protected Script p2shScript;
50 protected Address address;
51
52 public Federation(List<FederationMember> members, Instant creationTime, long creationBlockNumber, NetworkParameters btcParams) {
53 // Sorting members ensures same order of federation members for same members
54 // Immutability provides protection against unwanted modification, thus making the Federation instance
55 // effectively immutable
56 this.members = Collections.unmodifiableList(members.stream().sorted(FederationMember.BTC_RSK_MST_PUBKEYS_COMPARATOR).collect(Collectors.toList()));
57
58 this.creationTime = creationTime.truncatedTo(ChronoUnit.MILLIS);
59 this.creationBlockNumber = creationBlockNumber;
60 this.btcParams = btcParams;
61
62 // Calculated once on-demand
63 this.redeemScript = null;
64 this.p2shScript = null;
65 this.address = null;
66 }
67
68 public List<FederationMember> getMembers() {
69 // Safe to return members since
70 // both list and instances are immutable
71 return members;
72 }
73
74 public List<BtcECKey> getBtcPublicKeys() {
75 // Copy instances since we don't control
76 // immutability of BtcECKey instances
77 return members.stream()
78 .map(m -> m.getBtcPublicKey().getPubKey())
79 .map(BtcECKey::fromPublicOnly)
80 .collect(Collectors.toList());
81 }
82
83 public int getNumberOfSignaturesRequired() {
84 return members.size() / 2 + 1;
85 }
86
87 public Instant getCreationTime() {
88 return creationTime;
89 }
90
91 public NetworkParameters getBtcParams() {
92 return btcParams;
93 }
94
95 public long getCreationBlockNumber() {
96 return creationBlockNumber;
97 }
98
99 public Script getRedeemScript() {
100 if (redeemScript == null) {
101 redeemScript = ScriptBuilder.createRedeemScript(getNumberOfSignaturesRequired(), getBtcPublicKeys());
102 }
103
104 return redeemScript;
105 }
106
107 public Script getStandardRedeemScript() {
108 return getRedeemScript();
109 }
110
111 public Script getP2SHScript() {
112 if (p2shScript == null) {
113 p2shScript = ScriptBuilder.createP2SHOutputScript(getNumberOfSignaturesRequired(), getBtcPublicKeys());
114 }
115
116 return p2shScript;
117 }
118
119 public Address getAddress() {
120 if (address == null) {
121 address = Address.fromP2SHScript(btcParams, getP2SHScript());
122 }
123
124 return address;
125 }
126
127 public int getSize() {
128 return members.size();
129 }
130
131 public Integer getBtcPublicKeyIndex(BtcECKey key) {
132 for (int i = 0; i < members.size(); i++) {
133 // note that this comparison doesn't take into account
134 // key compression
135 if (Arrays.equals(key.getPubKey(), members.get(i).getBtcPublicKey().getPubKey())) {
136 return i;
137 }
138 }
139
140 return null;
141 }
142
143 public boolean hasBtcPublicKey(BtcECKey key) {
144 return getBtcPublicKeyIndex(key) != null;
145 }
146
147 public boolean hasMemberWithRskAddress(byte[] address) {
148 return members.stream()
149 .anyMatch(m -> Arrays.equals(m.getRskPublicKey().getAddress(), address));
150 }
151
152 public boolean isMember(FederationMember federationMember){
153 return this.members.contains(federationMember);
154 }
155
156 @Override
157 public String toString() {
158 return String.format("%d of %d signatures federation", getNumberOfSignaturesRequired(), members.size());
159 }
160
161 @Override
162 public boolean equals(Object other) {
163 if (this == other) {
164 return true;
165 }
166
167 if (other == null || this.getClass() != other.getClass()) {
168 return false;
169 }
170
171 Federation otherFederation = (Federation) other;
172
173 return this.getNumberOfSignaturesRequired() == otherFederation.getNumberOfSignaturesRequired() &&
174 this.getSize() == otherFederation.getSize() &&
175 this.getCreationTime().equals(otherFederation.getCreationTime()) &&
176 this.creationBlockNumber == otherFederation.creationBlockNumber &&
177 this.btcParams.equals(otherFederation.btcParams) &&
178 this.members.equals(otherFederation.members);
179 }
180
181 @Override
182 public int hashCode() {
183 // Can use java.util.Objects.hash since all of Instant, int and List<BtcECKey> have
184 // well-defined hashCode()s
185 return Objects.hash(
186 getCreationTime(),
187 this.creationBlockNumber,
188 getNumberOfSignaturesRequired(),
189 getBtcPublicKeys()
190 );
191 }
192 }