Skip to content

Commit

Permalink
library function for api call "anchors" (#130)
Browse files Browse the repository at this point in the history
* anchors support

* allow request for height 0

* anchor unit test
  • Loading branch information
WhoSoup authored and PaulBernier committed Dec 19, 2020
1 parent 0966b26 commit 8f69401
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 0 deletions.
132 changes: 132 additions & 0 deletions anchors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package factom

import (
"bytes"
"encoding/json"
"fmt"
"strings"
)

// Anchors is an anchors response from factomd.
// Note that Ethereum or Bitcoin can be nil
type Anchors struct {
Height uint32 `json:"directoryblockheight"`
KeyMR string `json:"directoryblockkeymr"`
Bitcoin *AnchorBitcoin `json:"bitcoin"`
Ethereum *AnchorEthereum `json:"ethereum"`
}

// AnchorBitcoin is the bitcoin specific anchor
type AnchorBitcoin struct {
TransactionHash string `json:"transactionhash"`
BlockHash string `json:"blockhash"`
}

// AnchorEthereum is the ethereum specific anchor
type AnchorEthereum struct {
RecordHeight int64 `json:"recordheight"`
DBHeightMax int64 `json:"dbheightmax"`
DBHeightMin int64 `json:"dbheightmin"`
WindowMR string `json:"windowmr"`
MerkleBranch []MerkleNode `json:"merklebranch"`

ContractAddress string `json:"contractaddress"`
TxID string `json:"txid"`
BlockHash string `json:"blockhash"`
TxIndex int64 `json:"txindex"`
}

// MerkleNode is part of the ethereum anchor
type MerkleNode struct {
Left string `json:"left,omitempty"`
Right string `json:"right,omitempty"`
Top string `json:"top,omitempty"`
}

func (a *Anchors) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "Height: %d\n", a.Height)
fmt.Fprintf(&sb, "KeyMR: %s\n", a.KeyMR)

if a.Bitcoin != nil {
fmt.Fprintf(&sb, "Bitcoin {\n")
fmt.Fprintf(&sb, " TransactionHash: %s\n", a.Bitcoin.TransactionHash)
fmt.Fprintf(&sb, " BlockHash: %s\n", a.Bitcoin.BlockHash)
fmt.Fprintf(&sb, "}\n")
} else {
fmt.Fprintf(&sb, "Bitcoin {}\n")
}

if a.Ethereum != nil {
fmt.Fprintf(&sb, "Ethereum {\n")
fmt.Fprintf(&sb, " RecordHeight: %d\n", a.Ethereum.RecordHeight)
fmt.Fprintf(&sb, " DBHeightMax: %d\n", a.Ethereum.DBHeightMax)
fmt.Fprintf(&sb, " DBHeightMin: %d\n", a.Ethereum.DBHeightMin)
fmt.Fprintf(&sb, " WindowMR: %s\n", a.Ethereum.WindowMR)
fmt.Fprintf(&sb, " MerkleBranch {\n")
for _, branch := range a.Ethereum.MerkleBranch {
fmt.Fprintf(&sb, " Branch {\n")
fmt.Fprintf(&sb, " Left: %s\n", branch.Left)
fmt.Fprintf(&sb, " Right: %s\n", branch.Right)
fmt.Fprintf(&sb, " Top: %s\n", branch.Top)
fmt.Fprintf(&sb, " Branch }\n")
}
fmt.Fprintf(&sb, " }\n")
fmt.Fprintf(&sb, " ContractAddress: %s\n", a.Ethereum.ContractAddress)
fmt.Fprintf(&sb, " TxID: %s\n", a.Ethereum.TxID)
fmt.Fprintf(&sb, " BlockHash: %s\n", a.Ethereum.BlockHash)
fmt.Fprintf(&sb, " TxIndex: %d\n", a.Ethereum.TxIndex)
fmt.Fprintf(&sb, "}\n")
} else {
fmt.Fprintf(&sb, "Ethereum {}\n")
}

return sb.String()
}

// UnmarshalJSON is an unmarshaller that handles the variable response from factomd
func (a *Anchors) UnmarshalJSON(data []byte) error {
type tmp *Anchors // unmarshal into a new type to prevent infinite loop
// json can't unmarshal a bool into a struct, but it can recognize a null pointer
data = bytes.Replace(data, []byte("\"ethereum\":false"), []byte("\"ethereum\":null"), -1)
data = bytes.Replace(data, []byte("\"bitcoin\":false"), []byte("\"bitcoin\":null"), -1)
return json.Unmarshal(data, tmp(a))
}

func getAnchors(hash string, height int64) (*Anchors, error) {
var params interface{}
if hash != "" {
params = hashRequest{Hash: hash}
} else {
params = heightRequest{Height: height}
}
req := NewJSON2Request("anchors", APICounter(), params)
resp, err := factomdRequest(req)
if err != nil {
return nil, err
}
if resp.Error != nil {
return nil, resp.Error
}
var res Anchors
err = json.Unmarshal(resp.Result, &res)
if err != nil {
return nil, err
}

return &res, nil
}

// GetAnchors retrieves the bitcoin and ethereum anchors from factod.
// Hash can be entry hash, entry block keymr, factoid block keymr,
// admin block lookup hash, entry credit block header hash, or
// directory block keymr
func GetAnchors(hash string) (*Anchors, error) {
return getAnchors(hash, 0)
}

// GetAnchorsByHeight retrieves the bitcoin and ethereum anchors for
// a specific height
func GetAnchorsByHeight(height int64) (*Anchors, error) {
return getAnchors("", height)
}
107 changes: 107 additions & 0 deletions anchors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package factom

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

func TestGetAnchors(t *testing.T) {
factomdResponse := `{"jsonrpc":"2.0","id":0,"result":{"directoryblockheight":200000,"directoryblockkeymr":"ce86fc790dd1462aea255adaa64e2f21c871995df2c2c119352d869fa1d7269f","bitcoin":{"transactionhash":"6d2d1e506528ae3b476d70fb05517bbbb152a4698a23ff78b4d87249027f53ca","blockhash":"0000000000000000000234e270b3fa6de63caad8a319731db5643ddb16b80cdf"},"ethereum":{"recordheight":200001,"dbheightmax":200000,"dbheightmin":199001,"windowmr":"935480547a2545161438da05136ec4238d88e7f8e1075687d8292fcafc1d0b22","merklebranch":[{"left":"a92a4460a9555c8b57a282e7ad4514d0f5aa8a612963da79db0e7cede6299bcd","right":"ce86fc790dd1462aea255adaa64e2f21c871995df2c2c119352d869fa1d7269f","top":"bbbf494fff20d8fbada47980498a96e49fba920fd1ca4e0586af4961e4318354"},{"left":"b41ffe5f9b710edf6bcf1a6d3e92685155411111d640f2b2224ab15f04c46161","right":"bbbf494fff20d8fbada47980498a96e49fba920fd1ca4e0586af4961e4318354","top":"ca1e7d59dc295c48ebddf6c068826584fb5adb38798f5cac7f964509627825f5"},{"left":"c919c0fb94900f3016c75a7f9f992c79aa7db126db6c6c3b1af92bbee174bda6","right":"ca1e7d59dc295c48ebddf6c068826584fb5adb38798f5cac7f964509627825f5","top":"3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda"},{"left":"3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda","right":"3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda","top":"5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e"},{"left":"5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e","right":"5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e","top":"a7ab3a72f5e754f3e10f04b9f7f2e50f36c688776b521918a755e62fd6c2da34"},{"left":"5e42d2f5ba59338b317369bbc53fbb70c094364ae8c15c9a291dad65dec9d839","right":"a7ab3a72f5e754f3e10f04b9f7f2e50f36c688776b521918a755e62fd6c2da34","top":"bc5c336d7e53b6d9f2832038c69a78eb7300bd539b6002d44f450a4d2c979aa3"},{"left":"31041410eb7bd6a9fabe974a0f4eabe477ffbbf5806fdaee5da98faff63b8142","right":"bc5c336d7e53b6d9f2832038c69a78eb7300bd539b6002d44f450a4d2c979aa3","top":"f9b5967e2acadfac93bc2141ee9f85b0649f89452e44e4f8b1cbe877457a0da4"},{"left":"04aa1b2d35ea99becd92bcfccc7406aea37287a58425b83349d0fc8cc7bec443","right":"f9b5967e2acadfac93bc2141ee9f85b0649f89452e44e4f8b1cbe877457a0da4","top":"77eca087628e243bf2914162be6c4aefaf736ed56e8c125b955cef3c48a1ab4b"},{"left":"be8394e3f3a205ad37afb8af778695e2914f071b3d215e586a93288fd692eb00","right":"77eca087628e243bf2914162be6c4aefaf736ed56e8c125b955cef3c48a1ab4b","top":"12aa5f990bd209dfaa83d9ea050e96e3b44e54ffee02252028fe969da3ab690d"},{"left":"2bdead51afe10d07c5dc5b29351fedd4a13849261359a6f22100dcc7ff50543a","right":"12aa5f990bd209dfaa83d9ea050e96e3b44e54ffee02252028fe969da3ab690d","top":"935480547a2545161438da05136ec4238d88e7f8e1075687d8292fcafc1d0b22"}],"contractaddress":"0xfac701d9554a008e48b6307fb90457ba3959e8a8","txid":"0xd57492c9d505e3052c454acdfc3768bc3eb8859c91829654346503ef2dcb6a23","blockhash":"0x49e8f3c394079e6c3964fdf943ea9759475d4ca5aec90c9a695be07efdd88d32","txindex":31}}}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, factomdResponse)
}))
defer ts.Close()

SetFactomdServer(ts.URL[7:])

response, err := GetAnchors("ce86fc790dd1462aea255adaa64e2f21c871995df2c2c119352d869fa1d7269f") // irrelevant, hardcoded above
if err != nil {
t.Error(err)
}
response2, err := GetAnchorsByHeight(200000) // irrelevant, hardcoded above
if err != nil {
t.Error(err)
}

received1 := fmt.Sprintf("%+v", response)
received2 := fmt.Sprintf("%+v", response2)
expected := `Height: 200000
KeyMR: ce86fc790dd1462aea255adaa64e2f21c871995df2c2c119352d869fa1d7269f
Bitcoin {
TransactionHash: 6d2d1e506528ae3b476d70fb05517bbbb152a4698a23ff78b4d87249027f53ca
BlockHash: 0000000000000000000234e270b3fa6de63caad8a319731db5643ddb16b80cdf
}
Ethereum {
RecordHeight: 200001
DBHeightMax: 200000
DBHeightMin: 199001
WindowMR: 935480547a2545161438da05136ec4238d88e7f8e1075687d8292fcafc1d0b22
MerkleBranch {
Branch {
Left: a92a4460a9555c8b57a282e7ad4514d0f5aa8a612963da79db0e7cede6299bcd
Right: ce86fc790dd1462aea255adaa64e2f21c871995df2c2c119352d869fa1d7269f
Top: bbbf494fff20d8fbada47980498a96e49fba920fd1ca4e0586af4961e4318354
Branch }
Branch {
Left: b41ffe5f9b710edf6bcf1a6d3e92685155411111d640f2b2224ab15f04c46161
Right: bbbf494fff20d8fbada47980498a96e49fba920fd1ca4e0586af4961e4318354
Top: ca1e7d59dc295c48ebddf6c068826584fb5adb38798f5cac7f964509627825f5
Branch }
Branch {
Left: c919c0fb94900f3016c75a7f9f992c79aa7db126db6c6c3b1af92bbee174bda6
Right: ca1e7d59dc295c48ebddf6c068826584fb5adb38798f5cac7f964509627825f5
Top: 3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda
Branch }
Branch {
Left: 3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda
Right: 3c8e97e32b4dbc0e4d82f066217492ca72ca722b91dbc764c311c39a9196bdda
Top: 5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e
Branch }
Branch {
Left: 5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e
Right: 5713abb24c8a8480f5c04bfdb8668a93e8b0a24a0562afbc7f362d665938a89e
Top: a7ab3a72f5e754f3e10f04b9f7f2e50f36c688776b521918a755e62fd6c2da34
Branch }
Branch {
Left: 5e42d2f5ba59338b317369bbc53fbb70c094364ae8c15c9a291dad65dec9d839
Right: a7ab3a72f5e754f3e10f04b9f7f2e50f36c688776b521918a755e62fd6c2da34
Top: bc5c336d7e53b6d9f2832038c69a78eb7300bd539b6002d44f450a4d2c979aa3
Branch }
Branch {
Left: 31041410eb7bd6a9fabe974a0f4eabe477ffbbf5806fdaee5da98faff63b8142
Right: bc5c336d7e53b6d9f2832038c69a78eb7300bd539b6002d44f450a4d2c979aa3
Top: f9b5967e2acadfac93bc2141ee9f85b0649f89452e44e4f8b1cbe877457a0da4
Branch }
Branch {
Left: 04aa1b2d35ea99becd92bcfccc7406aea37287a58425b83349d0fc8cc7bec443
Right: f9b5967e2acadfac93bc2141ee9f85b0649f89452e44e4f8b1cbe877457a0da4
Top: 77eca087628e243bf2914162be6c4aefaf736ed56e8c125b955cef3c48a1ab4b
Branch }
Branch {
Left: be8394e3f3a205ad37afb8af778695e2914f071b3d215e586a93288fd692eb00
Right: 77eca087628e243bf2914162be6c4aefaf736ed56e8c125b955cef3c48a1ab4b
Top: 12aa5f990bd209dfaa83d9ea050e96e3b44e54ffee02252028fe969da3ab690d
Branch }
Branch {
Left: 2bdead51afe10d07c5dc5b29351fedd4a13849261359a6f22100dcc7ff50543a
Right: 12aa5f990bd209dfaa83d9ea050e96e3b44e54ffee02252028fe969da3ab690d
Top: 935480547a2545161438da05136ec4238d88e7f8e1075687d8292fcafc1d0b22
Branch }
}
ContractAddress: 0xfac701d9554a008e48b6307fb90457ba3959e8a8
TxID: 0xd57492c9d505e3052c454acdfc3768bc3eb8859c91829654346503ef2dcb6a23
BlockHash: 0x49e8f3c394079e6c3964fdf943ea9759475d4ca5aec90c9a695be07efdd88d32
TxIndex: 31
}
`
if received1 != expected {
t.Errorf("GetAnchors() expected:%s\nreceived:%s", expected, received1)
}
if received2 != expected {
t.Errorf("GetAnchorsByHeight() expected:%s\nreceived:%s", expected, received2)
}

}

0 comments on commit 8f69401

Please sign in to comment.