Skip to content

Commit

Permalink
Add the ability to create and send a voting transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelKim20 authored and Geod24 committed Sep 20, 2020
1 parent ea51cc2 commit dc835c6
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "boa-sdk-ts",
"version": "0.0.5",
"version": "0.0.6",
"description": "The TypeScript BOA SDK libary",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -16,7 +16,6 @@
"url": "git+https://github.com/bpfkorea/boa-sdk-ts.git"
},
"keywords": [
"BOA",
"SDK",
"TypeScript"
],
Expand Down
114 changes: 113 additions & 1 deletion src/modules/data/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
*******************************************************************************/

import { hashFull } from "./Hash";
import { KeyPair, Seed } from "./KeyPair";
import { TxInput } from './TxInput';
import { TxOutput } from './TxOutput';
import * as Utils from '../utils'

import { SmartBuffer } from 'smart-buffer';

Expand Down Expand Up @@ -45,13 +48,19 @@ export class Transaction
*/
public outputs: TxOutput[];

/**
* The data to store
*/
public data: SmartBuffer;

/**
* Constructor
* @param type The type of the transaction
* @param inputs The array of references to the unspent output of the previous transaction
* @param outputs The array of newly created outputs
* @param data The data to store
*/
constructor (type?: number, inputs?: TxInput[], outputs?: TxOutput[])
constructor (type?: number, inputs?: TxInput[], outputs?: TxOutput[], data?: Buffer)
{
if (type !== undefined)
this.type = type;
Expand All @@ -67,6 +76,10 @@ export class Transaction
this.outputs = outputs;
else
this.outputs = [];

this.data = new SmartBuffer();
if (data != undefined)
this.data.writeBuffer(data);
}

/**
Expand All @@ -80,5 +93,104 @@ export class Transaction
elem.computeHash(buffer);
for (let elem of this.outputs)
elem.computeHash(buffer);

this.data.readOffset = 0;
buffer.writeBuffer(this.data.readBuffer())
}

/**
* Creates a new transaction
* @param inputs An array of 1 or more UTXOs to be spent
* @param outputs An array of 1 or more output
* @param keys An array of length matching `inputs` which are the keys controlling the UTXOs
* @param data The data to store
* @returns The instance of Transaction
*/
public static create (inputs: Array<TxInput>, outputs: Array<TxOutput>, keys: Array<Seed>, data: Buffer): Transaction
{
if (inputs.length == 0)
throw new Error ("The number of inputs is 0.");

if (outputs.length == 0)
throw new Error ("The number of outputs is 0.");

if (inputs.length != keys.length)
throw new Error ("The number of inputs and keys are different.");

let key_pairs: Array<KeyPair> = [];
for (let elem of keys)
key_pairs.push(KeyPair.fromSeed(elem));

let tx = new Transaction(TxType.Payment, inputs, outputs, data);
let tx_hash = hashFull(tx);
for (let idx = 0; idx < tx.inputs.length; idx++)
tx.inputs[idx].signature.data.set(key_pairs[idx].secret.sign(tx_hash.data).data)

return tx;
}

/**
* Gets the JSON object
* @returns The JSON object
* ex) {"type": 0, "inputs": inputs, "outputs": outputs, "data": data}
*/
public toObject (): ITransaction
{
let inputs = [];
for (let elem of this.inputs)
inputs.push(
{
"previous": elem.previous.toString(),
"index": elem.index,
"signature": elem.signature.toString()
}
);

let outputs = [];
for (let elem of this.outputs)
outputs.push(
{
"value": elem.value.toString(),
"address": elem.address.toString(),
}
);

this.data.readOffset = 0;
return {
"type": this.type,
"inputs": inputs,
"outputs": outputs,
"data": Utils.writeToString(this.data.readBuffer())
}
}
}

/**
* The interface of Transaction input for JSON
*/
interface ITxInput
{
previous: string;
index: number;
signature: string;
}

/**
* The interface of Transaction input for JSON
*/
interface ITxOutput
{
value: string;
address: string;
}

/**
* The interface of VoteData for JSON
*/
interface ITransaction
{
type: number;
inputs: Array<ITxInput>;
outputs: Array<ITxOutput>;
data: string;
}
52 changes: 51 additions & 1 deletion src/modules/net/BOAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
*******************************************************************************/

import { Hash, hash } from '../data/Hash';
import { Hash, hash, hashFull } from '../data/Hash';
import { Request } from './Request';
import { Validator } from '../data/Validator';
import { Seed, Transaction, TxInput, TxOutput } from "../..";

import { AxiosResponse, AxiosError } from 'axios';
import uri from 'urijs';
Expand Down Expand Up @@ -221,6 +222,55 @@ export class BOAClient
resolve(height);
});
}

/**
* Register the vote data
* @param inputs An array of 1 or more UTXOs to be spent
* @param outputs An array of 1 or more output
* @param keys An array of length matching `inputs` which are the keys controlling the UTXOs
* @param data The data to store
* @returns Returns true if success, otherwise returns false
*/
public registerVoteData (inputs: Array<TxInput>, outputs: Array<TxOutput>, keys: Array<Seed>, data: Buffer): Promise<boolean>
{
return new Promise<boolean>((resolve, reject) =>
{
try
{
let tx = Transaction.create(inputs, outputs, keys, data);

// TODO: Send a transaction to Agora
// ex)
/*
let url = uri(this.agora_url)
.directory("put_transaction");
Request.put(url.toString(), tx.toObject())
.then((response: AxiosResponse) =>
{
if (response.status == 200)
{
resolve(true);
}
else
{
// It is not yet defined in Stoa.
reject(new Error(response.statusText));
}
})
.catch((reason: any) =>
{
reject(handleNetworkError(reason));
});
*/

resolve(true);
} catch (err)
{
reject(err);
}
});
}
}

export interface IsValidPreimageResponse
Expand Down
132 changes: 132 additions & 0 deletions tests/BOAClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,4 +558,136 @@ describe ('BOA Client', () =>
doneIt();
});
});

it ('Test creating a vote data', () =>
{
let inputs = [
new boasdk.TxInput(
boasdk.Hash.createFromString("0x81a326afa790003c32517a2a" +
"2556613004e6147edac28d576cf7bcc2daadf4bb60be1f644c2" +
"29b775e7894844ec66b2d70ddf407b8196b46bc1dfe42061c74" +
"97"),
0
),
new boasdk.TxInput(
boasdk.Hash.createFromString("0xb82cb96710af2e9804c59d1f" +
"1e1679f8b8b69f4c0f6cd79c8c12f365dd766c09aaa4febcc18" +
"b3665d33301cb248ac7afd343ac7b98b27beaf246ad12d3b321" +
"9a"),
0
),
new boasdk.TxInput(
boasdk.Hash.createFromString("0x4028965b7408566a66e4cf8c" +
"603a1cdebc7659a3e693d36d2fdcb39b196da967914f40ef496" +
"6d5b4b1f4b3aae00fbd68ffe8808b070464c2a101d44f4d7b01" +
"70"),
0
)
];

let outputs = [
new boasdk.TxOutput(
"100000000",
boasdk.PublicKey.fromString("GDD5RFGBIUAFCOXQA246BOUPHCK" +
"7ZL2NSHDU7DVAPNPTJJKVPJMNLQFW")
)
];

let keys = [
boasdk.Seed.fromString("SDAKFNYEIAORZKKCYRILFQKLLOCNPL5SWJ3Y" +
"Y5NM3ZH6GJSZGXHZEPQS"),
boasdk.Seed.fromString("SAXA7RLGWM5I7Q34WBKXWLDPZ3NHFHATOZG7" +
"UUOG5ZGZCM7J64OLTJOT"),
boasdk.Seed.fromString("SDWAMFTNWY6XLZ2FDGBEMBYIXJTQSSA6OKSP" +
"H2YVLZH7NDE3LDFC2AJR")
];

let vote_tx = boasdk.Transaction.create(
inputs,
outputs,
keys,
Buffer.from("vote data")
);

let expected_object = {
type: 0,
inputs: [
{
previous: '0x81a326afa790003c32517a2a2556613004e6147edac28d576cf7bcc2daadf4bb60be1f644c229b775e7894844ec66b2d70ddf407b8196b46bc1dfe42061c7497',
index: 0,
signature: '0x00e435ab1f3dc92e9ba1bd9b10322bf5da143e13996cae2ccb208a03c83cec254010a775bc1bfbc896b333347d69ced6da7fca486f41af9b91ee6c1a11c3f204'
},
{
previous: '0xb82cb96710af2e9804c59d1f1e1679f8b8b69f4c0f6cd79c8c12f365dd766c09aaa4febcc18b3665d33301cb248ac7afd343ac7b98b27beaf246ad12d3b3219a',
index: 0,
signature: '0x007543e3195de306fe9fecef7f5e4ae03933985f6343f7a64e4383e0f5691eb151a95752be352c75d3d6a3aff2c3882faa4b65634594fee8c43af7a0273a02bb'
},
{
previous: '0x4028965b7408566a66e4cf8c603a1cdebc7659a3e693d36d2fdcb39b196da967914f40ef4966d5b4b1f4b3aae00fbd68ffe8808b070464c2a101d44f4d7b0170',
index: 0,
signature: '0x0e799ffbcc388e29ad9d76ca77634c22adfab4dcea925098758506667cd764a0d897e7d48abd3dbcf830da062111c37703a900fff676d557c4ef16a6d4067ff3'
}
],
outputs: [
{
value: '0x0000000005f5e100',
address: 'GDD5RFGBIUAFCOXQA246BOUPHCK7ZL2NSHDU7DVAPNPTJJKVPJMNLQFW'
}
],
data: '0x617461642065746f76'
};

let obj = vote_tx.toObject();
assert.deepStrictEqual(obj, expected_object);

// Verify the signature
let tx_hash = boasdk.hashFull(vote_tx);
for (let idx = 0; idx < vote_tx.inputs.length; idx++)
{
let key_pair = boasdk.KeyPair.fromSeed(keys[idx]);
assert.ok(key_pair.address.verify(vote_tx.inputs[idx].signature, tx_hash.data));
}
});

it ('Test sending a vote data', async () =>
{
// Set URL
let uri = URI("http://localhost").port(port);
let agora_uri = URI("http://localhost").port(agora_port);

// Create BOA Client
let boa_client = new boasdk.BOAClient(uri.toString(), agora_uri.toString());

try
{
let res = await boa_client.registerVoteData(
[
new boasdk.TxInput(
boasdk.Hash.createFromString("0x81a326afa790003c32517a2a" +
"2556613004e6147edac28d576cf7bcc2daadf4bb60be1f644c2" +
"29b775e7894844ec66b2d70ddf407b8196b46bc1dfe42061c74" +
"97"),
0
)
],
[
new boasdk.TxOutput(
"100000000",
boasdk.PublicKey.fromString("GDD5RFGBIUAFCOXQA246BOUPHCK" +
"7ZL2NSHDU7DVAPNPTJJKVPJMNLQFW")
)
],
[
boasdk.Seed.fromString("SDAKFNYEIAORZKKCYRILFQKLLOCNPL5SWJ3Y" +
"Y5NM3ZH6GJSZGXHZEPQS")
],
Buffer.from("vote data")
);
assert.ok(res);
}
catch (err)
{
assert.fail(err);
}
});
});

0 comments on commit dc835c6

Please sign in to comment.