DEVELOPMENT OF REST-BASED APIs FOR BLOCKCHAIN APPLICATIONS
As the core logics for implementing a blockchain is completed, it is essential to incorporate the additional capabilities on top of this core logic. Alternatively, the primary objective is to formulate a network of nodes that are running blockchain applications, which can be interacted with each other through a common API. Hence, each node in the network is going to run the same API in order to expose common methods that can be used to read each other’s information. The Express server is used to implement various HTTP post and get requests. The core concept of API is to support an overall blockchain network. The HTTP GET and POST requests are given in Listing 10.15 and Listing 10.16, repectively.
LISTING 10.15 HTTP GET REQUESTS FOR BLOCKCHAIN API
app.get(1/api/blocks', (req, res) => { res.j son(blockchain.chain);
}>; ‘
app.get(1/api/blocks/length1, (req, res) => { res. j son (blockchain. chain. length) ,- }>; ‘
app.get(1/api/blocks/:id', (req, res) => { const { id } = req.params;
LISTING 10.15 (Continued) HTTP GET REQUESTS FOR BLOCKCHAIN API
const { length } = blockchain.chain,-
constblocksReversed = blockchain.chain.slice().reverse(); letstartlndex = (id-1) * 5; letendlndex = id * 5;
startlndex = startlndex< length ? startlndex : length; endlndex = endlndex< length ? endlndex : length; res.json(blocksReversed.slice(startlndex, endlndex));
}>; '
app.get(1/api/transaction-pool-map (req, res) => { res.j son(transactionPool.transactionMap);
}>; "
app.get(1/api/mine-transactions1, (req, res) => { transactionMiner .mineTransactions () ,- res. redirect (1 /api/blocks 1) ,- }>;
app.get(1/api/wallet-info1, (req, res) => { const address = wallet.publicKey; res.j son({ address,
balance; Wallet.calculateBalance({ chain: blockchain. chain, address })
});
}>;
app.get(1/api/known-addresses', (req, res) => {
constaddressMap = {};
for (let block ofblockchain.chain) {
for (let transaction ofblock.data) {
const recipient = Object.keys(transaction.outputMap); recipient.forEach(recipient =>addressMap [recipient]
= recipient);
}
}
res.j son(Object.keys(addressMap));
}>; "
app.get('* 1, (req, res) => {
res.sendFile(path.join(_dirname, 1 client/dist/index.
html1)) ,-
}>;
LISTING 10.16 HTTP POST REQUEST FOR BLOCKCHAIN API
app.post(1/api/mine', (req, res) => { const { data } = req.body; blockchain.addBlock({ data }); pubsub.broadcastChainO res. redirect (1 /api/blocks 1) ,- }>;
app.post(1/api/transact', (req, res) => { const { amount, recipient } = req.body; let transaction = transactionPool
.existingTransaction({ inputAddress: wallet. publicKey }); try {
if (transaction) {
transaction.update({ senderWallet: wallet, recipient, amount });
} else {
transaction = wallet.createTransaction({
recipient,
amount,
chain: blockchain.chain
}>;
}
} catch(error) {
returnres.status(400).json({ type: 'error', message: error.message });
}
transactionPool. setTransaction (transaction) ,- pubsub.broadcastTransaction(transaction); res.json({ type: 'success', transaction });
}>; "
constsyncWithRootState = () => {
request({ url: '${ROOT_NODE_ADDRESS}/api/blocks“ }, (error, response, body) => { if (!error &&response.statusCode === 200) { constrootChain = JSON.parse(body);
console.log('replace chain on a sync with', rootchain); blockchain.replaceChain(rootchain);
}
});
request ({ url: ''${ROOT_NODE_ADDRESS}/api/transaction- pool-map' }, (error, response, body) => { if (!error &&response.statusCode === 200) { constrootTransactionPoolMap = JSON.parse(body); console.log(1 replace transaction pool map on a sync with1, rootTransactionPoolMap);
LISTING 10.16 (Continued) HTTP POST REQUEST FOR BLOCKCHAIN API
transactionPool.setMap(rootTransactionPoolMap);
}
});
b
if (isDevelopment) { constwalletFoo = new Wallet(); constwalletBar = new Wallet();
constgenerateWalletTransaction = ({ wallet, recipient, amount }) => {
const transaction = wallet.createTransaction({ recipient, amount, chain: blockchain.chain });
transactionPool. setTransaction (transaction) ,-
};
constwalletAction = () =>generateWalletTransaction({ wallet, recipient: walletFoo.publicKey, amount: 5 });
constwalletFooAction = () =>generateWalletTransaction({ wallet: walletFoo, recipient: walletBar.publicKey, amount: 10 });
constwalletBarAction = () =>generateWalletTransaction({ wallet: walletBar, recipient: wallet.publicKey, amount: 15 });
for (let i=0; i<20; i++) { if (i%3 === 0) { walletAction(); walletFooAction () ,-
} elseif (i%3 === 1) { walletAction(); walletBarAction();
} else {
walletFooAction(); walletBarAction();
}
transactionMiner .mineTransactions () ,-
}
}
let PEER_PORT;
if (process.env.GENERATE_PEER_PORT === 'true') { PEER_PORT = DEFAULT_PORT + Math.ceil(Math. random() * 1000);
}
LISTING 10.16 (Continued) HTTP POST REQUEST FOR BLOCKCHAIN API
const PORT = process.env.PORT || PEER_PORT || DEFAULT_PORT; app.listen(PORT, () => {
console.log('listening at localhost:${PORT}'); if (PORT !== DEFAULT_PORT) { syncWithRootState() ,- }
WALLETS IN CRYPTOCURRENCY
A wallet in cryptocurrency may be a hardware device, medium, software or a service that stocks the key pairs that will be used to verify the rights, credit or debit the cryptocurrencies. Here, the public key of a wallet may be used to credit the cryptocurrencies from any other wallet whereas the private key is used to debit cryptocurrency from this wallet to any other wallet. The code and test-case for transactions using a wallet is given in Listing 10.17 and Listing 10.18, respectively.
LISTING 10.17 CODE FOR TRANSACTIONS USING A WALLET
class Wallet { constructor() {
this.balance = STARTING_BALANCE; this.keyPair = ec.genKeyPair();
this.publicKey = this.keyPair.getPublic().encode(1 hex');
}
sign(data) {
returnthis.keyPair.sign(cryptoHash(data))
}
createTransaction({ recipient, amount, chain }) { if (chain) {
CContinued)
LISTING 10.17 (Continued) CODE FOR TRANSACTIONS USING A WALLET
this.balance = Wallet.calculateBalance({ chain,
address: this.publicKey
}>;
}
if (amount >this.balance) {
thrownew Error('Amount exceeds balance');
}
returnnew Transaction({ senderWallet: this, recipient, amount });
}
staticcalculateBalance({ chain, address }) { lethasConductedTransaction = false; letoutputsTotal = 0;
for (let i=chain. length-1,- i>0; i--) { const block = chain [i];
for (let transaction ofblock.data) { if (transaction.input.address === address) { hasConductedTransaction = true;
}
constaddressOutput = transaction. outputMap [address] if (addressOutput) {
outputsTotal = outputsTotal + addressOutput;
}
}
if (hasConductedTransaction) { break;
}
}
returnhasConductedTransaction ? outputsTotal : STARTING_ BALANCE + outputsTotal;
}
};
LISTING 10.18 TEST-CASE FOR WALLET
describe(1 Wallet', () => {
let wallet;
beforeEach(() => { wallet = new Wallet();
});
it{'has a 'balance'1, () => {
expect(wallet).toHaveProperty(1 balance1);
});
it('has a 'publicKey'', () => {
expect(wallet).toHaveProperty('publicKey');
});
describe('signing data', () => { const data = 'foobar',-
it('verifies a signature', () => { expect(
verifySignature({ publicKey: wallet.publicKey, data,
signature: wallet.sign(data)
})
).toBe(true);
});
it('does not verify an invalid signature', () => { expect (
verifySignature({ publicKey: wallet.publicKey, data,
signature: new Wallet().sign(data)
})
).toBe(false) ;
});
});
describe('createTransaction()', () => {
describe('and the amount exceeds the balance', () => {
it('throws an error', () => {
expect(() =>wallet.createTransaction({ amount: 999999, recipient: 'foo-recipient' }))
LISTING 10.18 (Continued) TEST-CASE FOR WALLET
.toThrow('Amount exceeds balance');
}>;
});
describe(1 and the amount is valid', () => { let transaction, amount, recipient;
beforeEach(() => { amount = 50;
recipient = 'foo-recipient'; transaction = wallet.
createTransaction({ amount, recipient });
}>;
it('creates an instance of 'Transaction'1, () => { expect(transaction instanceof Transaction).toBe(true);
}>;
it('matches the transaction input with the wallet', () => { expect(transaction.input.address).toEqual(wallet. publicKey);
});
it('outputs the amount the recipient', () => { expect (transaction. outputMap [recipient]) . toEqual (amount) ,- });
});
describe('and a chain is passed', () => { it('calls 'Wallet.calculateBalance'1, () => {
constcalculateBalanceMock = jest.fnO;
constoriginalCalculateBalance = Wallet. calculateBalance,-
Wallet.calculateBalance = calculateBalanceMock;
wallet.createTransaction({ recipient: 'foo1, amount: 10,
chain: newBlockchain().chain
}>;
expect (calculateBalanceMock) . toHaveBeenCalled () ,-
(:Continued)
LISTING 10.18 (Continued) TEST-CASE FOR WALLET
Wallet.calculateBalance = originalCalculateBalance;
}>;
});
});
describe(1calculateBalance()1 , () => {
letblockchain;
beforeEach(() => { blockchain = newBlockchain();
});
describe(1 and there are no outputs for the wallet', () => { it('returns the 'STARTINGJBALANCE'', () => {
expect (
Wallet.calculateBalance({ chain: blockchain.chain, address: wallet.publicKey })
).toEqual(STARTING_BALANCE);
});
});
describe('and there are outputs for the wallet', () => { lettransactionOne, transactionTwo;
beforeEach(() => {
transactionOne = new Wallet().createTransaction({ recipient: wallet.publicKey, amount: 5 0 });
transactionTwo = new Wallet ().createTransaction({ recipient: wallet.publicKey, amount: 6 0 });
blockchain.addBlock({ data: [transactionOne, transactionTwo] });
});
it('adds the sum of all outputs to the wallet balance',
0 => {
expect (
Wallet.calculateBalance({
LISTING 10.18 (Continued) TEST-CASE FOR WALLET
chain: blockchain.chain, address: wallet.publicKey })
).toEqual(
STARTING_BALANCE +
transactionOne.outputMap[wallet.publicKey] + transactionTwo.outputMap[wallet.publicKey]
) ;
}>;
describe(1 and the wallet has made a transaction', () => { letrecentTransaction;
beforeEach(() => {
recentTransaction = wallet.createTransaction({ recipient: 'foo-address', amount: 3 0 });
blockchain.addBlock({ data: [recentTransaction] });
}>;
it ('returns the output amount of the recent transaction', 0 => { expect (
Wallet.calculateBalance({ chain: blockchain.chain, address: wallet.publicKey })
).toEqual(recentTransaction.outputMap[wallet. publicKey]);
}>;
describe('and there are outputs next to and after the recent transaction', () => {
letsameBlockTransaction, nextBlockTransaction; beforeEach(() => {
recentTransaction = wallet.createTransaction({ recipient: 'later-foo-address', amount: 6 0 }>;
sameBlockTransaction = Transaction.rewardTransaction({ minerWallet: wallet });
LISTING 10.18 (Continued) TEST-CASE FOR WALLET
blockchain.addBlock({ data: [recentTransaction, sameBlockTransaction] });
nextBlockTransaction = new Wallet().createTransaction({ recipient: wallet.publicKey, amount: 75 }>;
blockchain.addBlock({ data: [nextBlockTransaction] });
});
it(1 includes the output amounts in the returned balance1, 0 => { expect (
Wallet.calculateBalance({ chain: blockchain.chain, address: wallet.publicKey })
) . toEqual(
recentTransaction.outputMap[wallet.publicKey] + sameBlockTransaction.outputMap[wallet.publicKey] + nextBlockTransaction.outputMap[wallet.publicKey]
) ;
});
});
}>;
});
});
});
Transaction Pool
The transaction pool is a data structure that is used to collect the transactions that can be implemented as an array or a map with a key value structure. Apart from this, it has to support the following behaviors:
- • Collects a unique set of transaction objects
- • Supports updating of transactions whenever change has been submitted by a wallet
- • Rewrites multiple transactions in the inner collection This is shown in Listing 10.19.
LISTING 10.19 CODE FOR IMPLEMENTING TRANSACTION POOL
const Transaction = require(1./transaction');
classTransactionPool {
constructor() {
this.transactionMap = {};
}
clear() {
this.transactionMap = {};
}
setTransaction(transaction) {
this.transactionMap[transaction.id] = transaction;
}
setMap(transactionMap) {
this.transactionMap = transactionMap;
}
existingTransaction({ inputAddress }) { const transactions = Object .values (this. transactionMap) returntransactions.find(transaction =transaction.input. address === inputAddress);
}
validTransactions() {
returnObject.values(this.transactionMap).filter( transaction =transaction.validTransaction(transaction)
) ;
}
clearBlockchainTransactions({ chain }) {
for (let i=l; i const block = chain[i]; for (let transaction of block.data) { if (this.transactionMap[transaction.id]) { deletethis.transactionMap[transaction.id] ; } } } } } module.exports = TransactionPool; The tests to validate the code: LISTING 10.20 TEST-CASE FOR TRANSACTION POOL constTransactionPool = require('./transaction-pool'); const Transaction = require('./transaction'); const Wallet = require('./index'); constBlockchain = require('../blockchain'); describe(1TransactionPool1, () => { lettransactionPool, transaction, senderWallet; beforeEach(() => { transactionPool = new TransactionPool () ,- senderWallet = new Wallet (),- transaction = new Transaction({ senderWallet, recipient: 'fake-recipient', amount: 5 0 }); }); describe(1setTransaction()' , () => { it('adds a transaction', () => { transactionPool. setTransaction (transaction) ,- expect(transactionPool.transactionMap[transaction.id] ) .toBe(transaction); }); }); describe('existingTransaction()', () => { it ('returns an existing transaction given an input address', () => { transactionPool. setTransaction (transaction) ,- expect ( transactionPool.existingTransaction ({ inputAddress: senderWallet.publicKey }) ).toBe(transaction); }); }); describe('validTransactions()', () => { letvalidTransactions, errorMock,- beforeEach(() => { validTransactions = []; errorMock = jest.fnO; global.console.error = errorMock; for (let i=0; i<10; i++) { transaction = new Transaction({ senderWallet, recipient: 'any-recipient', amount: 3 0 }) ,- (Continued) LISTING 10.20 (Continued) TEST-CASE FOR TRANSACTION POOL if (i%3===0) { transaction.input.amount = 999999; } else if (i%3===l) { transaction.input.signature = new Wallet().sign('foo'); } else { validTransactions.push(transaction) ,- } transactionPool. setTransaction (transaction) ,- } }>; it('returns valid transaction', () => { expect(transactionPool.validTransactions()). toEqual(validTransactions); }>; it('logs errors for the invalid transactions', () => { transactionPool .validTransactions () ,- expect (errorMock) .toHaveBeenCalledf) ,- }); }); describe(1 clear()1, () => { it ('clears the transactions', () => { transactionPool. clear () ,- expect(transactionPool.transactionMap).toEqual({}); }>; }); describe('clearBlockchainTransactions()', () => { it('clears the pool of any existing blockchain transactions', () => { constblockchain = new Blockchain(); constexpectedTransactionMap = {}; for (let i=0; i<6; i++) { const transaction = new Wallet().createTransaction({ recipient: 'foo', amount: 20 }); transactionPool. setTransaction (transaction) ,- if (i%2===0) { blockchain.addBlock({ data: [transaction] }) } else { expectedTransactionMap[transaction.id] = transaction; } } LISTING 10.20 (Continued) TEST-CASE FOR TRANSACTION POOL transactionPool.clearBlockchainTransactions({ chain: blockchain.chain }); expect(transactionPool.transactionMap).toEqual(expectedTr ansactionMap) }>; }); }); Cryptocurrencies are new kind of digital assets that are revolutionizing the manner by which the international markets are associated with each other, thereby ruliong out the obstacles surrounding normative national currencies and exchange rates. Blockchain is a distributed ledger that bundles the transactions as blocks after the successful verification and validation using the consensus algorithm implemented in the nodes of P2P networks, which are later chained together using the hash codes of each block. The cryptocurrencies use the blockchain network to do the transactions in secured way without any centralized authority. This chapter elucidates a TDD- based methodology to create a new blockchain using Javascript. Since the application of blockchain technology is not restricted solely to business, the steps explained can be used in other domains as well. Kumar, S. R., & Gayathri, N. (2016). Trust based data transmission mechanism in MANET using sOLSR. In Annual Convention of the Computer Society of India (pp. 169-180). Springer, Singapore. Nagasubramanian, G., Sakthivel, R. K., Patan. R. et al. (2020). Securing e-health records using keyless signature infrastructure blockchain technology in the cloud. Neural Computing & Applications, 32, 639-647. https://doi.org/10.1007/s00521-018-3915-lCONCLUSION
REFERENCES