Skip to content

Commit 8dfe826

Browse files
authored
Merge pull request #3679 from ArkEcosystem/2.6
2 parents 0db5f03 + 3a9fd23 commit 8dfe826

File tree

53 files changed

+869
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+869
-274
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
## [2.6.36] - 2020-05-04
11+
12+
### Fixed
13+
14+
- Update vote balance with htlc locked balance on vote transactions ([#3669])
15+
- Use sorted array (instead of tree) for storing transactions by fee and nonce ([#3678])
16+
1017
## [2.6.34] - 2020-04-28
1118

1219
### Fixed
@@ -956,6 +963,7 @@ Closed security vulnerabilities:
956963
- Initial Release
957964

958965
[unreleased]: https://github.com/ARKEcosystem/core/compare/master...develop
966+
[2.6.36]: https://github.com/ARKEcosystem/core/compare/2.6.34...2.6.36
959967
[2.6.34]: https://github.com/ARKEcosystem/core/compare/2.6.31...2.6.34
960968
[2.6.31]: https://github.com/ARKEcosystem/core/compare/2.6.30...2.6.31
961969
[2.6.30]: https://github.com/ARKEcosystem/core/compare/2.6.29...2.6.30
@@ -1509,6 +1517,8 @@ Closed security vulnerabilities:
15091517
[#3659]: https://github.com/ARKEcosystem/core/pull/3659
15101518
[#3665]: https://github.com/ARKEcosystem/core/pull/3665
15111519
[#3667]: https://github.com/ARKEcosystem/core/pull/3667
1520+
[#3669]: https://github.com/ARKEcosystem/core/pull/3669
1521+
[#3678]: https://github.com/ARKEcosystem/core/pull/3678
15121522
[032caa1b990e91937e4bc1561bc1aeaeca9e37d]: https://github.com/ARKEcosystem/core/commit/032caa1b990e91937e4bc1561bc1aeaeca9e37d9
15131523
[1209a36366c8fd3ba31fab2463011b7ce1a7d84]: https://github.com/ARKEcosystem/core/commit/1209a36366c8fd3ba31fab2463011b7ce1a7d844
15141524
[34749bf84bcec3fecd0098c0d42f52deb1f6ba4]: https://github.com/ARKEcosystem/core/commit/34749bf84bcec3fecd0098c0d42f52deb1f6ba4a

__tests__/functional/transaction-forging/htlc-lock.test.ts

+77-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Crypto, Enums, Identities } from "@arkecosystem/crypto";
1+
import { Crypto, Enums, Identities, Utils } from "@arkecosystem/crypto";
2+
import got from "got";
23
import { TransactionFactory } from "../../helpers/transaction-factory";
34
import { secrets } from "../../utils/config/testnet/delegates.json";
45
import * as support from "./__support__";
@@ -130,4 +131,79 @@ describe("Transaction Forging - HTLC Lock", () => {
130131
await support.snoozeForBlock(1);
131132
await expect(transaction.id).toBeForged();
132133
});
134+
135+
it("should update delegates vote balance using locked balance when voting and unvoting delegates", async () => {
136+
const newWalletPassphrase = "this is a new wallet passphrase";
137+
// Initial Funds
138+
const initialBalance = 100 * 1e8;
139+
const initialFunds = TransactionFactory.transfer(
140+
Identities.Address.fromPassphrase(newWalletPassphrase),
141+
initialBalance,
142+
)
143+
.withPassphrase(secrets[0])
144+
.createOne();
145+
146+
await expect(initialFunds).toBeAccepted();
147+
await support.snoozeForBlock(1);
148+
await expect(initialFunds.id).toBeForged();
149+
150+
const delegateToVote = Identities.PublicKey.fromPassphrase(secrets[9]);
151+
const { body } = await got.get(`http://localhost:4003/api/v2/delegates/${delegateToVote}`);
152+
const parsedBody = JSON.parse(body);
153+
const initialDelegateVoteValance = Utils.BigNumber.make(parsedBody.data.votes);
154+
155+
// Submit a vote
156+
const vote = TransactionFactory.vote(delegateToVote)
157+
.withPassphrase(newWalletPassphrase)
158+
.createOne();
159+
160+
await expect(vote).toBeAccepted();
161+
await support.snoozeForBlock(1);
162+
await expect(vote.id).toBeForged();
163+
164+
const expectedBalanceAfterVote = initialDelegateVoteValance.plus(initialBalance).minus(vote.fee);
165+
await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterVote.toString());
166+
167+
// Submit htlc lock transaction
168+
const lockTransaction = TransactionFactory.htlcLock({
169+
secretHash: "0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454",
170+
expiration: {
171+
type: EpochTimestamp,
172+
value: Crypto.Slots.getTime() + 1000,
173+
},
174+
})
175+
.withPassphrase(newWalletPassphrase)
176+
.createOne();
177+
178+
await expect(lockTransaction).toBeAccepted();
179+
await support.snoozeForBlock(1);
180+
await expect(lockTransaction.id).toBeForged();
181+
182+
const expectedBalanceAfterLock = expectedBalanceAfterVote.minus(lockTransaction.fee);
183+
await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterLock.toString());
184+
185+
// Unvote
186+
const unvote = TransactionFactory.unvote(delegateToVote)
187+
.withPassphrase(newWalletPassphrase)
188+
.createOne();
189+
190+
await expect(unvote).toBeAccepted();
191+
await support.snoozeForBlock(1);
192+
await expect(unvote.id).toBeForged();
193+
194+
const expectedBalanceAfterUnvote = initialDelegateVoteValance;
195+
await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterUnvote.toString());
196+
197+
// Vote again
198+
const voteAgain = TransactionFactory.vote(delegateToVote)
199+
.withPassphrase(newWalletPassphrase)
200+
.createOne();
201+
202+
await expect(voteAgain).toBeAccepted();
203+
await support.snoozeForBlock(1);
204+
await expect(voteAgain.id).toBeForged();
205+
206+
const expectedBalanceAfterVoteAgain = expectedBalanceAfterLock.minus(unvote.fee).minus(voteAgain.fee);
207+
await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterVoteAgain.toString());
208+
});
133209
});

__tests__/unit/core-transaction-pool/connection.test.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -1087,16 +1087,19 @@ describe("Connection", () => {
10871087
expect(prevSender).toEqual(curSender);
10881088
}
10891089

1090-
if (prevSender !== curSender) {
1091-
let j;
1092-
for (j = i - 2; j >= 0 && sortedTransactions[j].data.senderPublicKey === prevSender; j--) {
1093-
// Find the leftmost transaction in a sequence of transactions from the same
1094-
// sender, ending at prevTransaction. That leftmost transaction's fee must
1095-
// be greater or equal to the fee of curTransaction.
1096-
}
1097-
j++;
1098-
expect(sortedTransactions[j].data.fee.isGreaterThanEqual(curTransaction.data.fee)).toBeTrue();
1099-
}
1090+
// This is not true anymore with current implementation, which is simpler and more performant
1091+
// than the previous one, but it does not do this fee optimization (which is a very specific
1092+
// case and is not worth it currently)
1093+
// if (prevSender !== curSender) {
1094+
// let j;
1095+
// for (j = i - 2; j >= 0 && sortedTransactions[j].data.senderPublicKey === prevSender; j--) {
1096+
// // Find the leftmost transaction in a sequence of transactions from the same
1097+
// // sender, ending at prevTransaction. That leftmost transaction's fee must
1098+
// // be greater or equal to the fee of curTransaction.
1099+
// }
1100+
// j++;
1101+
// expect(sortedTransactions[j].data.fee.isGreaterThanEqual(curTransaction.data.fee)).toBeTrue();
1102+
// }
11001103

11011104
if (lastNonceBySender[curSender] !== undefined) {
11021105
expect(lastNonceBySender[curSender].isLessThan(curTransaction.data.nonce)).toBeTrue();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import "./mocks/core-container";
2+
3+
import { Managers, Transactions, Utils } from "@arkecosystem/crypto";
4+
import { Memory } from "../../../packages/core-transaction-pool/src/memory";
5+
6+
Managers.configManager.setFromPreset("testnet");
7+
Managers.configManager.getMilestone().aip11 = true;
8+
9+
describe("Memory", () => {
10+
describe("getLowestFeeLastNonce", () => {
11+
it("should get the lowest fee which also is the last nonce from the sender - when a 'last nonce tx' is among the 100 lowest fee txs", () => {
12+
const txs = [];
13+
let lastNonceLowestFeeTx;
14+
for (let i = 0; i < 100; i++) {
15+
for (let nonce = 1; nonce <= 10; nonce++) {
16+
const passphrase = `random ${i}`;
17+
const address = "AWLzbT4z7KntQ3D9LTz7ukPHyXy6mNy2YQ";
18+
const transaction = Transactions.BuilderFactory.transfer()
19+
.nonce(nonce.toString())
20+
.recipientId(address)
21+
.amount("1")
22+
.fee((1e6 - 1000 * nonce - i).toString()) // the last nonce tx will be the lowest fee
23+
.sign(passphrase)
24+
.build();
25+
26+
txs.push(transaction);
27+
}
28+
const lastNonceTx = txs[txs.length - 1];
29+
lastNonceLowestFeeTx = lastNonceLowestFeeTx
30+
? lastNonceTx.data.fee.isLessThan(lastNonceLowestFeeTx.data.fee)
31+
? lastNonceTx
32+
: lastNonceLowestFeeTx
33+
: lastNonceTx;
34+
}
35+
36+
const memory = new Memory(120);
37+
for (const tx of txs) {
38+
memory.remember(tx);
39+
}
40+
41+
expect(memory.getLowestFeeLastNonce()).toEqual(lastNonceLowestFeeTx);
42+
});
43+
44+
it("should get the lowest fee which also is the last nonce from the sender - when the 100 lowest fees are not a 'last nonce tx'", () => {
45+
const txs = [];
46+
let lastNonceLowestFeeTx;
47+
for (let i = 0; i < 100; i++) {
48+
for (let nonce = 1; nonce <= 10; nonce++) {
49+
const passphrase = `random ${i}`;
50+
const address = "AWLzbT4z7KntQ3D9LTz7ukPHyXy6mNy2YQ";
51+
const transaction = Transactions.BuilderFactory.transfer()
52+
.nonce(nonce.toString())
53+
.recipientId(address)
54+
.amount("1")
55+
.fee((1000 * nonce + i).toString()) // the last nonce tx will be the biggest fee
56+
.sign(passphrase)
57+
.build();
58+
59+
txs.push(transaction);
60+
}
61+
const lastNonceTx = txs[txs.length - 1];
62+
lastNonceLowestFeeTx = lastNonceLowestFeeTx
63+
? lastNonceTx.data.fee.isLessThan(lastNonceLowestFeeTx.data.fee)
64+
? lastNonceTx
65+
: lastNonceLowestFeeTx
66+
: lastNonceTx;
67+
}
68+
69+
const memory = new Memory(120);
70+
for (const tx of txs) {
71+
memory.remember(tx);
72+
}
73+
74+
expect(memory.getLowestFeeLastNonce()).toEqual(lastNonceLowestFeeTx);
75+
});
76+
});
77+
78+
describe("sortedByFee", () => {
79+
it.each([[undefined], [100], [190], [565], [1550]])(
80+
"should keep nonce order when sorting by fee and nonce - with limit %s",
81+
limit => {
82+
const txs = [];
83+
for (let i = 0; i < 50; i++) {
84+
for (let nonce = 1; nonce <= 50; nonce++) {
85+
const passphrase = `random ${i}`;
86+
const address = "AWLzbT4z7KntQ3D9LTz7ukPHyXy6mNy2YQ";
87+
const transaction = Transactions.BuilderFactory.transfer()
88+
.nonce(nonce.toString())
89+
.recipientId(address)
90+
.amount("1")
91+
.fee(Math.floor(Math.random() * 100000).toString()) //
92+
.sign(passphrase)
93+
.build();
94+
95+
txs.push(transaction);
96+
}
97+
}
98+
99+
const memory = new Memory(120);
100+
for (const tx of txs) {
101+
memory.remember(tx);
102+
}
103+
104+
const byFee = [...memory.sortedByFee(limit)];
105+
106+
// checking that nonces are in order
107+
const lastNonceBySender: { [id: string]: Utils.BigNumber } = {};
108+
for (const tx of byFee) {
109+
const sender = tx.data.senderPublicKey;
110+
if (lastNonceBySender[sender]) {
111+
expect(tx.data.nonce).toEqual(lastNonceBySender[sender].plus(1));
112+
} else {
113+
expect(tx.data.nonce).toEqual(Utils.BigNumber.ONE);
114+
}
115+
lastNonceBySender[sender] = tx.data.nonce;
116+
}
117+
},
118+
);
119+
});
120+
});

0 commit comments

Comments
 (0)