fetch.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package stacks
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "strings"
  8. "github.com/certusone/wormhole/node/pkg/common"
  9. )
  10. type (
  11. StacksStxTransferEvent struct {
  12. Amount string `json:"amount"`
  13. Memo string `json:"memo"`
  14. Recipient string `json:"recipient"`
  15. Sender string `json:"sender"`
  16. }
  17. StacksContractEvent struct {
  18. ContractIdentifier string `json:"contract_identifier"`
  19. RawValue string `json:"raw_value"`
  20. Topic string `json:"topic"`
  21. Value map[string]interface{} `json:"value"`
  22. }
  23. StacksEvent struct {
  24. Committed bool `json:"committed"`
  25. EventIndex uint64 `json:"event_index"`
  26. TxID string `json:"txid"`
  27. Type string `json:"type"`
  28. StxTransferEvent *StacksStxTransferEvent `json:"stx_transfer_event,omitempty"`
  29. ContractEvent *StacksContractEvent `json:"contract_event,omitempty"`
  30. }
  31. StacksV3TenureBlock struct {
  32. BlockId string `json:"block_id"`
  33. BlockHash string `json:"block_hash"`
  34. ParentBlockId string `json:"parent_block_id"`
  35. Height uint64 `json:"height"`
  36. }
  37. StacksV3TenureBlocksResponse struct {
  38. ConsensusHash string `json:"consensus_hash"`
  39. BurnBlockHeight uint64 `json:"burn_block_height"`
  40. BurnBlockHash string `json:"burn_block_hash"`
  41. StacksBlocks []StacksV3TenureBlock `json:"stacks_blocks"`
  42. }
  43. StacksV3TenureBlockTransaction struct {
  44. TxId string `json:"txid"`
  45. TxIndex uint32 `json:"tx_index"`
  46. Data map[string]interface{} `json:"data,omitempty"` // Transaction data structure
  47. Hex string `json:"hex,omitempty"` // Raw transaction hex
  48. Result map[string]interface{} `json:"result,omitempty"` // Transaction execution result
  49. ResultHex string `json:"result_hex,omitempty"` // Transaction execution result in hex
  50. StxBurned uint64 `json:"stx_burned,omitempty"` // STX burned in transaction
  51. ExecutionCost map[string]interface{} `json:"execution_cost,omitempty"` // Execution cost breakdown
  52. Events []StacksEvent `json:"events,omitempty"` // Transaction events
  53. PostConditionAborted bool `json:"post_condition_aborted,omitempty"` // Whether the post-condition was aborted
  54. VmError *string `json:"vm_error,omitempty"` // Runtime error message if transaction failed (null when successful)
  55. }
  56. StacksV3TenureBlockReplayResponse struct {
  57. BlockId string `json:"block_id"`
  58. BlockHash string `json:"block_hash"`
  59. BlockHeight uint64 `json:"block_height"`
  60. ParentBlockId string `json:"parent_block_id"`
  61. ConsensusHash string `json:"consensus_hash"`
  62. Fees uint64 `json:"fees"`
  63. TxMerkleRoot string `json:"tx_merkle_root"`
  64. StateIndexRoot string `json:"state_index_root"`
  65. Timestamp uint64 `json:"timestamp"`
  66. MinerSignature string `json:"miner_signature"`
  67. SignerSignature []string `json:"signer_signature"`
  68. Transactions []StacksV3TenureBlockTransaction `json:"transactions"`
  69. ValidMerkleRoot bool `json:"valid_merkle_root"`
  70. }
  71. StacksV3TransactionResponse struct {
  72. IndexBlockHash string `json:"index_block_hash"`
  73. Tx string `json:"tx"`
  74. Result string `json:"result"`
  75. }
  76. StacksV2PoxEpoch struct {
  77. EpochID string `json:"epoch_id"`
  78. StartHeight uint64 `json:"start_height"`
  79. EndHeight uint64 `json:"end_height"`
  80. }
  81. StacksV2PoxResponse struct {
  82. ContractID string `json:"contract_id"`
  83. FirstBurnchainBlockHeight uint64 `json:"first_burnchain_block_height"`
  84. CurrentBurnchainBlockHeight uint64 `json:"current_burnchain_block_height"`
  85. PreparePhaseBlockLength uint64 `json:"prepare_phase_block_length"`
  86. RewardPhaseBlockLength uint64 `json:"reward_phase_block_length"`
  87. Epochs []StacksV2PoxEpoch `json:"epochs"`
  88. }
  89. StacksV2InfoPoxAnchor struct {
  90. AnchorBlockHash string `json:"anchor_block_hash"`
  91. AnchorBlockTxid string `json:"anchor_block_txid"`
  92. }
  93. StacksV2InfoResponse struct {
  94. PeerVersion uint64 `json:"peer_version"`
  95. PoxConsensus string `json:"pox_consensus"`
  96. BurnBlockHeight uint64 `json:"burn_block_height"`
  97. StablePoxConsensus string `json:"stable_pox_consensus"`
  98. StableBurnBlockHeight uint64 `json:"stable_burn_block_height"`
  99. ServerVersion string `json:"server_version"`
  100. NetworkID uint64 `json:"network_id"`
  101. ParentNetworkID uint64 `json:"parent_network_id"`
  102. StacksTipHeight uint64 `json:"stacks_tip_height"`
  103. StacksTip string `json:"stacks_tip"`
  104. StacksTipConsensusHash string `json:"stacks_tip_consensus_hash"`
  105. GenesisChainStateHash string `json:"genesis_chainstate_hash"`
  106. UnanchoredTip *string `json:"unanchored_tip"`
  107. UnanchoredSeq *uint64 `json:"unanchored_seq"`
  108. TenureHeight uint64 `json:"tenure_height"`
  109. ExitAtBlockHeight *uint64 `json:"exit_at_block_height"`
  110. IsFullySynced bool `json:"is_fully_synced"`
  111. NodePublicKey string `json:"node_public_key"`
  112. NodePublicKeyHash string `json:"node_public_key_hash"`
  113. LastPoxAnchor *StacksV2InfoPoxAnchor `json:"last_pox_anchor"`
  114. Stackerdbs []string `json:"stackerdbs"`
  115. }
  116. )
  117. // Fetches a tenure and its blocks by Bitcoin (burn) block height
  118. func (w *Watcher) fetchTenureBlocksByBurnHeight(ctx context.Context, height uint64) (*StacksV3TenureBlocksResponse, error) {
  119. url := fmt.Sprintf("%s/v3/tenures/blocks/height/%d", w.rpcURL, height)
  120. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  121. if err != nil {
  122. return nil, fmt.Errorf("failed to create request: %w", err)
  123. }
  124. resp, err := http.DefaultClient.Do(req)
  125. if err != nil {
  126. return nil, fmt.Errorf("failed to fetch Bitcoin (burn) block: %w", err)
  127. }
  128. defer resp.Body.Close()
  129. if resp.StatusCode != http.StatusOK {
  130. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  131. }
  132. body, err := common.SafeRead(resp.Body)
  133. if err != nil {
  134. return nil, fmt.Errorf("failed to read response body: %w", err)
  135. }
  136. var tenureBlocks StacksV3TenureBlocksResponse
  137. if err := json.Unmarshal(body, &tenureBlocks); err != nil {
  138. return nil, fmt.Errorf("failed to parse response: %w", err)
  139. }
  140. return &tenureBlocks, nil
  141. }
  142. // Fetches block replay data including all transactions for a given block
  143. // Uses the v3 blocks/replay endpoint which includes vm_error for failed transactions
  144. func (w *Watcher) fetchStacksBlockReplay(ctx context.Context, blockId string) (*StacksV3TenureBlockReplayResponse, error) {
  145. url := fmt.Sprintf("%s/v3/blocks/replay/%s", w.rpcURL, blockId)
  146. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  147. if err != nil {
  148. return nil, fmt.Errorf("failed to create request: %w", err)
  149. }
  150. if w.rpcAuthToken != "" {
  151. req.Header.Set("Authorization", w.rpcAuthToken)
  152. }
  153. resp, err := http.DefaultClient.Do(req)
  154. if err != nil {
  155. return nil, fmt.Errorf("failed to fetch block replay: %w", err)
  156. }
  157. defer resp.Body.Close()
  158. if resp.StatusCode != http.StatusOK {
  159. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  160. }
  161. body, err := common.SafeRead(resp.Body)
  162. if err != nil {
  163. return nil, fmt.Errorf("failed to read response body: %w", err)
  164. }
  165. var replay StacksV3TenureBlockReplayResponse
  166. if err := json.Unmarshal(body, &replay); err != nil {
  167. return nil, fmt.Errorf("failed to parse response: %w", err)
  168. }
  169. return &replay, nil
  170. }
  171. // Fetches a transaction by its txid
  172. func (w *Watcher) fetchStacksTransactionByTxId(ctx context.Context, txID string) (*StacksV3TransactionResponse, error) {
  173. txID = strings.TrimPrefix(txID, "0x")
  174. url := fmt.Sprintf("%s/v3/transaction/%s", w.rpcURL, txID)
  175. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  176. if err != nil {
  177. return nil, fmt.Errorf("failed to create request: %w", err)
  178. }
  179. resp, err := http.DefaultClient.Do(req)
  180. if err != nil {
  181. return nil, fmt.Errorf("failed to fetch transaction: %w", err)
  182. }
  183. defer resp.Body.Close()
  184. if resp.StatusCode != http.StatusOK {
  185. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  186. }
  187. body, err := common.SafeRead(resp.Body)
  188. if err != nil {
  189. return nil, fmt.Errorf("failed to read response body: %w", err)
  190. }
  191. var tx StacksV3TransactionResponse
  192. if err := json.Unmarshal(body, &tx); err != nil {
  193. return nil, fmt.Errorf("failed to parse node transaction response: %w", err)
  194. }
  195. return &tx, nil
  196. }
  197. // Fetches PoX (Proof of Transfer) information including epoch data
  198. func (w *Watcher) fetchPoxInfo(ctx context.Context) (*StacksV2PoxResponse, error) {
  199. url := fmt.Sprintf("%s/v2/pox", w.rpcURL)
  200. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  201. if err != nil {
  202. return nil, fmt.Errorf("failed to create request: %w", err)
  203. }
  204. resp, err := http.DefaultClient.Do(req)
  205. if err != nil {
  206. return nil, fmt.Errorf("failed to fetch PoX info: %w", err)
  207. }
  208. defer resp.Body.Close()
  209. if resp.StatusCode != http.StatusOK {
  210. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  211. }
  212. body, err := common.SafeRead(resp.Body)
  213. if err != nil {
  214. return nil, fmt.Errorf("failed to read response body: %w", err)
  215. }
  216. var poxInfo StacksV2PoxResponse
  217. if err := json.Unmarshal(body, &poxInfo); err != nil {
  218. return nil, fmt.Errorf("failed to parse PoX info response: %w", err)
  219. }
  220. return &poxInfo, nil
  221. }
  222. // Fetches node information from the Stacks node
  223. func (w *Watcher) fetchNodeInfo(ctx context.Context) (*StacksV2InfoResponse, error) {
  224. url := fmt.Sprintf("%s/v2/info", w.rpcURL)
  225. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  226. if err != nil {
  227. return nil, fmt.Errorf("failed to create request: %w", err)
  228. }
  229. resp, err := http.DefaultClient.Do(req)
  230. if err != nil {
  231. return nil, fmt.Errorf("failed to fetch Stacks node info: %w", err)
  232. }
  233. defer resp.Body.Close()
  234. if resp.StatusCode != http.StatusOK {
  235. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  236. }
  237. body, err := common.SafeRead(resp.Body)
  238. if err != nil {
  239. return nil, fmt.Errorf("failed to read response body: %w", err)
  240. }
  241. var nodeInfo StacksV2InfoResponse
  242. if err := json.Unmarshal(body, &nodeInfo); err != nil {
  243. return nil, fmt.Errorf("failed to parse Stacks node info response: %w", err)
  244. }
  245. return &nodeInfo, nil
  246. }