Skip to content
This repository has been archived by the owner on Mar 16, 2022. It is now read-only.

Commit

Permalink
Vechain block observer added (#254)
Browse files Browse the repository at this point in the history
* Vechain block observer added

* minor fixes

* Minimized code in client and formatted

* comments for exported functions added

* added comments

* bugs fixed

* added transaction normalization test

* code formatted

* redesigned normalize function

* Fix gosimple rules
  • Loading branch information
vcoolish authored and vikmeup committed Aug 13, 2019
1 parent 4e15409 commit 11f56a9
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 33 deletions.
156 changes: 154 additions & 2 deletions platform/vechain/api.go
Expand Up @@ -26,6 +26,37 @@ func (p *Platform) Coin() coin.Coin {
}

const VeThorContract = "0x0000000000000000000000000000456e65726779"
const VeThorTransferEvent = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

// CurrentBlockNumber implementation of interface function which gets a current blockchain height
func (p *Platform) CurrentBlockNumber() (int64, error) {
cbi, err := p.client.GetCurrentBlockInfo()
if err != nil {
return 0, err
}
return cbi.BestBlockNum, nil
}

// GetBlockByNumber implementation of interface function which gets a block for push notification
func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
block, err := p.client.GetBlockByNumber(num)
if err != nil {
return nil, err
}

transactionsChan := p.getTransactions(block.Transactions)

var txs []blockatlas.Tx
for t := range transactionsChan {
txs = append(txs, NormalizeTransaction(t)...)
}

return &blockatlas.Block{
Number: num,
ID: block.ID,
Txs: txs,
}, nil
}

func (p *Platform) GetTxsByAddress(address string) (blockatlas.TxPage, error) {
return p.getTxsByAddress(address)
Expand Down Expand Up @@ -89,6 +120,32 @@ func (p *Platform) getTransactionReceipt(ids []string) chan *TransferReceipt {
return receiptsChan
}

func (p *Platform) getTransactions(ids []string) chan *NativeTransaction {
receiptsChan := make(chan *NativeTransaction, len(ids))

sem := util.NewSemaphore(16)
var wg sync.WaitGroup
wg.Add(len(ids))
for _, id := range ids {
go func(id string) {
defer wg.Done()
sem.Acquire()
defer sem.Release()
receipt, err := p.client.GetTransactionByID(id)
if err != nil {
logrus.WithError(err).WithField("platform", "vechain").
Warnf("Failed to get transaction for %s", id)
}
receiptsChan <- receipt
}(id)
}

wg.Wait()
close(receiptsChan)

return receiptsChan
}

func findTransferReceiptByTxID(receiptsChan chan *TransferReceipt, txID string) TransferReceipt {

var transferReceipt TransferReceipt
Expand Down Expand Up @@ -127,6 +184,13 @@ func (p *Platform) getTxsByAddress(address string) ([]blockatlas.Tx, error) {
return txs, nil
}

func formatHexToAddress(hex string) string {
if len(hex) > 26 {
return "0x" + hex[26:]
}
return hex
}

func NormalizeTransfer(receipt *TransferReceipt, clause *Clause) (tx blockatlas.Tx, ok bool) {
feeBase10, err := util.HexToDecimal(receipt.Receipt.Paid)
if err != nil {
Expand All @@ -150,7 +214,7 @@ func NormalizeTransfer(receipt *TransferReceipt, clause *Clause) (tx blockatlas.
Date: int64(time),
Type: blockatlas.TxTransfer,
Block: block,
Status: receipt.Receipt.Status(),
Status: ReceiptStatus(receipt.Receipt.Reverted),
Sequence: block,
Meta: blockatlas.Transfer{
Value: blockatlas.Amount(valueBase10),
Expand Down Expand Up @@ -184,7 +248,7 @@ func NormalizeTokenTransfer(t *TokenTransfer, receipt *TransferReceipt) (tx bloc
Date: t.Timestamp,
Type: blockatlas.TxNativeTokenTransfer,
Block: block,
Status: receipt.Receipt.Status(),
Status: ReceiptStatus(receipt.Receipt.Reverted),
Sequence: block,
Meta: blockatlas.NativeTokenTransfer{
Name: "VeThor Token",
Expand All @@ -197,3 +261,91 @@ func NormalizeTokenTransfer(t *TokenTransfer, receipt *TransferReceipt) (tx bloc
},
}, true
}

// NormalizeTransaction converts a VeChain VTHO token transaction into the generic model
func NormalizeTransaction(t *NativeTransaction) (txs []blockatlas.Tx) {

for outputIndex, output := range t.Receipt.Outputs {
//Normalizes vtho transfer events in given transaction
for eventIndex, event := range output.Events {
if len(event.Topics) == 3 && event.Topics[0] == VeThorTransferEvent {

feeBase10, err := util.HexToDecimal(t.Receipt.Paid)
if err != nil {
continue
}

valueBase10, err := util.HexToDecimal(t.Receipt.Outputs[outputIndex].Events[eventIndex].Data)
if err != nil {
continue
}
fee := blockatlas.Amount(feeBase10)
value := blockatlas.Amount(valueBase10)
fromHex := t.Receipt.Outputs[outputIndex].Events[eventIndex].Topics[1]
toHex := t.Receipt.Outputs[outputIndex].Events[eventIndex].Topics[2]
from := formatHexToAddress(fromHex)
to := formatHexToAddress(toHex)
block := t.Block

txs = append(txs, blockatlas.Tx{
ID: t.ID,
Coin: coin.VET,
From: from,
To: to,
Fee: fee,
Date: t.Timestamp,
Type: blockatlas.TxNativeTokenTransfer,
Block: block,
Status: ReceiptStatus(t.Receipt.Reverted),
Sequence: block,
Meta: blockatlas.NativeTokenTransfer{
Name: "VeThor Token",
Symbol: "VTHO",
TokenID: VeThorContract,
Decimals: 18,
Value: value,
From: from,
To: to,
},
})
}
}
//Normalizes transfers in given transaction
for transferIndex := range output.Transfers {
feeBase10, err := util.HexToDecimal(t.Receipt.Paid)
if err != nil {
continue
}

transfer := t.Receipt.Outputs[outputIndex].Transfers[transferIndex]
valueBase10, err := util.HexToDecimal(transfer.Amount)
if err != nil {
continue
}

fee := blockatlas.Amount(feeBase10)
time := t.Timestamp
block := t.Block

txs = append(txs, blockatlas.Tx{
ID: t.ID,
Coin: coin.VET,
From: transfer.Sender,
To: transfer.Recipient,
Fee: fee,
Date: time,
Type: blockatlas.TxTransfer,
Block: block,
Status: ReceiptStatus(t.Receipt.Reverted),
Sequence: block,
Meta: blockatlas.Transfer{
Value: blockatlas.Amount(valueBase10),
Symbol: coin.Coins[coin.VET].Symbol,
Decimals: coin.Coins[coin.VET].Decimals,
},
})
}
}

return txs
}
133 changes: 120 additions & 13 deletions platform/vechain/api_test.go
Expand Up @@ -3,10 +3,9 @@ package vechain
import (
"bytes"
"encoding/json"
"testing"

"github.com/trustwallet/blockatlas"
"github.com/trustwallet/blockatlas/coin"
"testing"
)

const transferReceipt = `{
Expand All @@ -29,7 +28,7 @@ const transferFailedReceipt = `{
"timestamp": 1556569300,
"receipt": {
"paid": "0x1236efcbcbb340000",
"reverted": true
"reverted": false
}
}`

Expand All @@ -41,11 +40,81 @@ const transferClause = `{
const tokenTransfer = `
{
"amount": "0x00000000000000000000000000000000000000000000000d8d726b7177a80000",
"block": 2465269,
"block": 2620166,
"origin": "0xb853d6a965fbc047aaa9f04d774d53861d7ed653",
"receiver": "0x9f3742c2c2fe66c7fca08d77d2262c22e3d56ac8",
"timestamp": 1555009870,
"txId": "0xd17dd968610fb4a39ab02a5d8827b26f4cdcd147fb4a4f7a5d5ab14066525d4b"
"timestamp": 1556569300,
"txId": "0x2b8776bd4679fa2afa28b55d66d4f6c7c77522fc878ce294d25e32475b704517"
}
`

const transaction = `
{
"block":2620166,
"blockRef":"0x003578d93e73a9ca",
"chainTag":74,
"clauses":[
{
"data":"0xa9059cbb0000000000000000000000009f3742c2c2fe66c7fca08d77d2262c22e3d56ac8000000000000000000000000000000000000000000000008ac7230489e800000",
"numValue":0,
"to":"0x0000000000000000000000000000456e65726779",
"txClauseIndex":0,
"txId":"0x2b8776bd4679fa2afa28b55d66d4f6c7c77522fc878ce294d25e32475b704517",
"value":"0x0"
}
],
"expiration":720,
"gas":160000,
"gasPriceCoef":0,
"id":"0x2b8776bd4679fa2afa28b55d66d4f6c7c77522fc878ce294d25e32475b704517",
"meta":{
"blockID":"0x003578db7b662faecc743f3a401515eef5baebe16c27e635f79bcfca3b8a39dc",
"blockNumber":3504347,
"blockTimestamp":1565458520
},
"nonce":"0x8e60abce86ae",
"numClauses":1,
"origin":"0xb853d6a965fbc047aaa9f04d774d53861d7ed653",
"receipt":{
"gasPayer":"0xa760bdcbf6c2935d2f1591a38f23251619f802ad",
"gasUsed":36582,
"meta":{
"blockID":"0x003578db7b662faecc743f3a401515eef5baebe16c27e635f79bcfca3b8a39dc",
"blockNumber":3504347,
"blockTimestamp":1565458520,
"txID":"0x2b8776bd4679fa2afa28b55d66d4f6c7c77522fc878ce294d25e32475b704517",
"txOrigin":"0xb853d6a965fbc047aaa9f04d774d53861d7ed653"
},
"outputs":[
{
"events":[
{
"address":"0x0000000000000000000000000000456e65726779",
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000b853d6a965fbc047aaa9f04d774d53861d7ed653",
"0x0000000000000000000000009f3742c2c2fe66c7fca08d77d2262c22e3d56ac8"
],
"data":"0x00000000000000000000000000000000000000000000000d8d726b7177a80000"
}
],
"transfers":[
{
"sender":"0xb853d6a965fbc047aaa9f04d774d53861d7ed653",
"recipient":"0xda623049a13df5c8a24f0d7713f4add4ab136b1f",
"amount":"0x29bde5885d7ac80000"
}
]
}
],
"paid":"0x1236efcbcbb340000",
"reverted":false,
"reward":"0x984d9c8dd8008000"
},
"reverted":0,
"size":191,
"timestamp":1556569300,
"totalValue":0
}
`

Expand All @@ -66,16 +135,16 @@ var expectedTransferTrx = blockatlas.Tx{
}

var expectedVeThorTrx = blockatlas.Tx{
ID: "0xd17dd968610fb4a39ab02a5d8827b26f4cdcd147fb4a4f7a5d5ab14066525d4b",
ID: "0x2b8776bd4679fa2afa28b55d66d4f6c7c77522fc878ce294d25e32475b704517",
Coin: coin.VET,
From: "0xb853d6a965fbc047aaa9f04d774d53861d7ed653",
To: "0x9f3742c2c2fe66c7fca08d77d2262c22e3d56ac8",
Fee: "21000000000000000000",
Date: 1555009870,
Date: 1556569300,
Type: blockatlas.TxNativeTokenTransfer,
Status: "failed",
Sequence: 2465269,
Block: 2465269,
Status: "completed",
Sequence: 2620166,
Block: 2620166,
Meta: blockatlas.NativeTokenTransfer{
Name: "VeThor Token",
Symbol: "VTHO",
Expand Down Expand Up @@ -115,7 +184,7 @@ func TestNormalizeTransfer(t *testing.T) {
var readyTx blockatlas.Tx
normTx, ok := NormalizeTransfer(&receipt, &clause)
if !ok {
t.Fatal("VeChain: Can't normalize transaction", readyTx)
t.Fatal("VeChain: Can't normalize transfer", readyTx)
}
readyTx = normTx

Expand Down Expand Up @@ -164,7 +233,7 @@ func TestNormalizeTokenTransfer(t *testing.T) {
var readyTx blockatlas.Tx
normTx, ok := NormalizeTokenTransfer(&tt, &receipt)
if !ok {
t.Fatal("VeChain: Can't normalize token transaction", readyTx)
t.Fatal("VeChain: Can't normalize token transfer", readyTx)
}
readyTx = normTx

Expand All @@ -185,3 +254,41 @@ func TestNormalizeTokenTransfer(t *testing.T) {
}
}
}

func TestNormalizeTransaction(t *testing.T) {
var tests = []struct {
Transaction string
ExpectedTransaction []blockatlas.Tx
}{
{transaction, []blockatlas.Tx{expectedVeThorTrx, expectedTransferTrx}},
}

for _, test := range tests {
var transaction NativeTransaction

tErr := json.Unmarshal([]byte(test.Transaction), &transaction)
if tErr != nil {
t.Fatal(tErr)
}

var readyTxs []blockatlas.Tx

readyTxs = append(readyTxs, NormalizeTransaction(&transaction)...)

actual, err := json.Marshal(&readyTxs)
if err != nil {
t.Fatal(err)
}

expectedTransactions, err := json.Marshal(&test.ExpectedTransaction)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(actual, expectedTransactions) {
println(string(actual))
println(string(expectedTransactions))
t.Error("Transactions not equal")
}
}
}

0 comments on commit 11f56a9

Please sign in to comment.