Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voting API #4

Merged
merged 29 commits into from
Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1e288e3
Add the initial voting functionality of the distributed democracy gro…
Mar 31, 2020
51f5e29
Extended git ignore to include the `.idea` folder
emieldesmidt Apr 2, 2020
2cc199b
Extended git ignore to include the `.idea` folder
emieldesmidt Apr 2, 2020
38dcc3e
Removed the idea folder from git
emieldesmidt Apr 2, 2020
60dea9f
Merge pull request #1 from emieldesmidt/initial_voting_to_superapp
PraveshMoelchand Apr 2, 2020
f08e43e
Update start vote method to use new api-approach
emieldesmidt Apr 2, 2020
387e9e7
Update respond to vote and countvote to account for any counterparty.
emieldesmidt Apr 2, 2020
fcba870
Initial test setup, updated gradle
emieldesmidt Apr 2, 2020
27d5dc9
Improved documentation for the other groups to use
emieldesmidt Apr 3, 2020
e903b1d
Made the trustchain community a param for the voting helper
emieldesmidt Apr 3, 2020
de77a9b
Tested start vote (NOTE, not working yet due to mock issues)
emieldesmidt Apr 3, 2020
0d4ee2e
retrieve voteSubject from the proposal block instead of seperate para…
emieldesmidt Apr 3, 2020
f7b5be6
fixed gradle log bug (thanks Matt)
emieldesmidt Apr 3, 2020
86b9e1d
fixed gradle bug (thanks Matt again)
emieldesmidt Apr 3, 2020
6495589
add validator
emieldesmidt Apr 3, 2020
bc2abc9
setup for count test
emieldesmidt Apr 3, 2020
0f09b04
improved voting, not quite there yet
emieldesmidt Apr 3, 2020
8c875ff
Add new peers.
emieldesmidt Apr 3, 2020
7742c3c
Removed unused lines
emieldesmidt Apr 3, 2020
fd18c8d
Improved testing, successfully mocked store
emieldesmidt Apr 5, 2020
89cd8d7
Fixed tests, temp commented out breaking if statement
emieldesmidt Apr 5, 2020
8fa6e85
Removed unused imports
emieldesmidt Apr 5, 2020
ca01e67
Fixed checking if voter is eligible to do so.
emieldesmidt Apr 5, 2020
499c288
improved eligible check, required tostring()
emieldesmidt Apr 5, 2020
7053e10
fix some linting warnings
emieldesmidt Apr 5, 2020
4fe8212
Merge branch 'dao' into updated_api
emieldesmidt Apr 5, 2020
181cb2c
Check on bin array content instead of string match
emieldesmidt Apr 5, 2020
6c81682
Merge remote-tracking branch 'origin/updated_api' into updated_api
emieldesmidt Apr 5, 2020
a1bdfba
removed redundant `toString()``
emieldesmidt Apr 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ android {
viewBinding {
enabled = true
}

testOptions {
unitTests.returnDefaultValues = true
}
}

dependencies {
api project(':ipv8-android')
api project(':ipv8')

// AndroidX
implementation 'androidx.appcompat:appcompat:1.1.0'
Expand All @@ -73,12 +78,21 @@ dependencies {

// Logging
implementation 'io.github.microutils:kotlin-logging:1.7.7'
implementation 'com.github.tony19:logback-android:2.0.0'
//implementation 'com.github.tony19:logback-android:2.0.0'

implementation 'com.mattskala:itemadapter:0.3'

// Crypto
implementation "com.goterl.lazycode:lazysodium-java:4.2.4"

// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3'
testImplementation "io.mockk:mockk:1.9.3"
testImplementation 'org.json:json:20190722'
testImplementation "com.squareup.sqldelight:sqlite-driver:$sqldelight_version"


}
12 changes: 12 additions & 0 deletions common/src/main/java/nl/tudelft/trustchain/common/DemoCommunity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import android.util.Log
import nl.tudelft.ipv8.IPv4Address
import nl.tudelft.ipv8.Community
import nl.tudelft.ipv8.Peer
import nl.tudelft.ipv8.android.IPv8Android
import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity
import nl.tudelft.ipv8.messaging.*
import nl.tudelft.ipv8.messaging.payload.IntroductionResponsePayload
import nl.tudelft.trustchain.common.util.VotingHelper
import java.util.*

class DemoCommunity : Community() {
Expand All @@ -15,6 +18,15 @@ class DemoCommunity : Community() {

val lastTrackerResponses = mutableMapOf<IPv4Address, Date>()

// Retrieve the trustchain community
private fun getTrustChainCommunity(): TrustChainCommunity {
return IPv8Android.getInstance().getOverlay()
?: throw IllegalStateException("TrustChainCommunity is not configured")
}

// Create a votingHelper instance for voting across the community
val votingHelper: VotingHelper = VotingHelper(getTrustChainCommunity())

override fun walkTo(address: IPv4Address) {
super.walkTo(address)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class TrustChainHelper(

/**
* Creates a new proposal block, using a text message as the transaction content.
* Optionally, the blockType can be passed as an argument. Default value is "demo_block".
*/
fun createProposalBlock(message: String, publicKey: ByteArray) {
val blockType = "demo_block"
fun createProposalBlock(message: String, publicKey: ByteArray, blockType: String = "demo_block") {
val transaction = mapOf("message" to message)
trustChainCommunity.createProposalBlock(blockType, transaction, publicKey)
}
Expand Down
172 changes: 172 additions & 0 deletions common/src/main/java/nl/tudelft/trustchain/common/util/VotingHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package nl.tudelft.trustchain.common.util

import android.annotation.SuppressLint
import android.util.Log
import nl.tudelft.ipv8.attestation.trustchain.EMPTY_PK
import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock
import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity
import nl.tudelft.ipv8.keyvault.PublicKey
import nl.tudelft.ipv8.keyvault.defaultCryptoProvider
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.*

/**
* Helper class for creating votes proposals, casting and counting.
* @param trustChainCommunity the community where the votes will be dealt.
*/
class VotingHelper(
trustChainCommunity: TrustChainCommunity
) {
private val votingBlock = "voting_block"

private val trustChainHelper: TrustChainHelper = TrustChainHelper(trustChainCommunity)

/**
* Initiate a vote amongst a set of peers.
* @param voteSubject the matter to be voted upon.
* @param peers list of the public keys of those eligible to vote.
*/
fun startVote(voteSubject: String, peers: List<PublicKey>) {
// TODO: Add vote ID to increase probability of uniqueness.

val voteList = JSONArray(peers)

// Create a JSON object containing the vote subject, as well as a log of the eligible voters
val voteJSON = JSONObject()
.put("VOTE_SUBJECT", voteSubject)
.put("VOTE_LIST", voteList)

val transaction = voteJSON.toString()

// Create any-counterparty block for the transaction
trustChainHelper.createProposalBlock(transaction, EMPTY_PK, votingBlock)
}

/**
* Respond to a proposal block on the trustchain, either agree with "YES" or disagree "NO".
* @param vote boolean value indicating the decision.
* @param proposalBlock TrustChainBlock of the proposalblock.
*/
fun respondToVote(vote: Boolean, proposalBlock: TrustChainBlock) {

// Parse the subject of the vote
val proposalSubject = try {
JSONObject(proposalBlock.transaction["message"].toString()).get("VOTE_SUBJECT")
} catch (e: JSONException) {
Log.e("vote-debug", "Invalidly formatted proposal block, perhaps not a proposal")
}

// Reply to the vote with YES or NO.
val voteReply = if (vote) "YES" else "NO"

// Create a JSON object containing the vote subject and the reply.
val voteJSON = JSONObject()
.put("VOTE_SUBJECT", proposalSubject)
.put("VOTE_REPLY", voteReply)

// Put the JSON string in the transaction's 'message' field.
val transaction = mapOf("message" to voteJSON.toString())

trustChainHelper.createAgreementBlock(proposalBlock, transaction)
}

/**
* Return the tally on a vote proposal in a pair(yes, no).
* @param voters list of the public keys of the eligible voters.
* @param voteSubject the matter to be voted upon.
* @param proposerKey the identifier of the vote proposer .
* @return Pair<Int, Int> indicating the election results.
*/
fun countVotes(
voters: List<PublicKey>,
voteSubject: String,
proposerKey: ByteArray
): Pair<Int, Int> {

// ArrayList for keeping track of already counted votes
val votes: MutableList<String> = ArrayList()

var yesCount = 0
var noCount = 0

// Crawl the chain of the proposer.
for (it in trustChainHelper.getChainByUser(proposerKey)) {
val blockPublicKey: PublicKey = defaultCryptoProvider.keyFromPublicBin(it.publicKey)

// Check whether vote has already been counted
if (votes.contains(it.publicKey.contentToString())) {
continue
}

// Skip all blocks which are not voting blocks
// and don't have a 'message' field in their transaction.
if (it.type != votingBlock || !it.transaction.containsKey("message")) {
continue
}

// Parse the 'message' field as JSON.
val voteJSON = try {
JSONObject(it.transaction["message"].toString())
} catch (e: JSONException) {
// Assume a malicious vote if it claims to be a vote but does not contain
// proper JSON.
handleInvalidVote(
"Block was a voting block but did not contain " +
"proper JSON in its message field: ${it.transaction["message"]}."
)
continue
}

// Assume a malicious vote if it does not have a VOTE_SUBJECT.
if (!voteJSON.has("VOTE_SUBJECT")) {
handleInvalidVote("Block type was a voting block but did not have a VOTE_SUBJECT.")
continue
}

// A block with another VOTE_SUBJECT belongs to another vote.
if (voteJSON.get("VOTE_SUBJECT") != voteSubject) {
// Block belongs to another vote.
continue
}

// A block with the same subject but no reply is the original vote proposal.
if (!voteJSON.has("VOTE_REPLY")) {
// Block is the initial vote proposal because it does not have a VOTE_REPLY field.
continue
}

// Check whether the voter is in voting list
@SuppressLint
if (!voters.any { v ->
val voteString = v.keyToBin()
voteString.contentEquals(blockPublicKey.keyToBin())
}) {
continue
}

// Add the votes, or assume a malicious vote if it is not YES or NO.
when (voteJSON.get("VOTE_REPLY")) {
"YES" -> {
yesCount++
votes.add(it.publicKey.contentToString())
}
"NO" -> {
noCount++
votes.add(it.publicKey.contentToString())
}
else -> handleInvalidVote("Vote was not 'YES' or 'NO' but: '${voteJSON.get("VOTE_REPLY")}'.")
}
}

return Pair(yesCount, noCount)
}

/**
* Helper function for debugging purposes
*/
private fun handleInvalidVote(errorType: String) {
Log.e("vote_debug", errorType)
}
}
Loading