fetch.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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.StatusNotFound {
  130. // Burn block was skipped, return empty tenure blocks
  131. return &StacksV3TenureBlocksResponse{
  132. BurnBlockHeight: height,
  133. StacksBlocks: []StacksV3TenureBlock{},
  134. }, nil
  135. }
  136. if resp.StatusCode != http.StatusOK {
  137. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  138. }
  139. body, err := common.SafeRead(resp.Body)
  140. if err != nil {
  141. return nil, fmt.Errorf("failed to read response body: %w", err)
  142. }
  143. var tenureBlocks StacksV3TenureBlocksResponse
  144. if err := json.Unmarshal(body, &tenureBlocks); err != nil {
  145. return nil, fmt.Errorf("failed to parse response: %w", err)
  146. }
  147. return &tenureBlocks, nil
  148. }
  149. // Fetches block replay data including all transactions for a given block
  150. // Uses the v3 blocks/replay endpoint which includes vm_error for failed transactions
  151. func (w *Watcher) fetchStacksBlockReplay(ctx context.Context, blockId string) (*StacksV3TenureBlockReplayResponse, error) {
  152. url := fmt.Sprintf("%s/v3/blocks/replay/%s", w.rpcURL, blockId)
  153. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  154. if err != nil {
  155. return nil, fmt.Errorf("failed to create request: %w", err)
  156. }
  157. if w.rpcAuthToken != "" {
  158. req.Header.Set("Authorization", w.rpcAuthToken)
  159. }
  160. resp, err := http.DefaultClient.Do(req)
  161. if err != nil {
  162. return nil, fmt.Errorf("failed to fetch block replay: %w", err)
  163. }
  164. defer resp.Body.Close()
  165. if resp.StatusCode != http.StatusOK {
  166. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  167. }
  168. body, err := common.SafeRead(resp.Body)
  169. if err != nil {
  170. return nil, fmt.Errorf("failed to read response body: %w", err)
  171. }
  172. var replay StacksV3TenureBlockReplayResponse
  173. if err := json.Unmarshal(body, &replay); err != nil {
  174. return nil, fmt.Errorf("failed to parse response: %w", err)
  175. }
  176. return &replay, nil
  177. }
  178. // Fetches a transaction by its txid
  179. func (w *Watcher) fetchStacksTransactionByTxId(ctx context.Context, txID string) (*StacksV3TransactionResponse, error) {
  180. txID = strings.TrimPrefix(txID, "0x")
  181. url := fmt.Sprintf("%s/v3/transaction/%s", w.rpcURL, txID)
  182. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  183. if err != nil {
  184. return nil, fmt.Errorf("failed to create request: %w", err)
  185. }
  186. resp, err := http.DefaultClient.Do(req)
  187. if err != nil {
  188. return nil, fmt.Errorf("failed to fetch transaction: %w", err)
  189. }
  190. defer resp.Body.Close()
  191. if resp.StatusCode != http.StatusOK {
  192. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  193. }
  194. body, err := common.SafeRead(resp.Body)
  195. if err != nil {
  196. return nil, fmt.Errorf("failed to read response body: %w", err)
  197. }
  198. var tx StacksV3TransactionResponse
  199. if err := json.Unmarshal(body, &tx); err != nil {
  200. return nil, fmt.Errorf("failed to parse node transaction response: %w", err)
  201. }
  202. return &tx, nil
  203. }
  204. // Fetches PoX (Proof of Transfer) information including epoch data
  205. func (w *Watcher) fetchPoxInfo(ctx context.Context) (*StacksV2PoxResponse, error) {
  206. url := fmt.Sprintf("%s/v2/pox", w.rpcURL)
  207. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  208. if err != nil {
  209. return nil, fmt.Errorf("failed to create request: %w", err)
  210. }
  211. resp, err := http.DefaultClient.Do(req)
  212. if err != nil {
  213. return nil, fmt.Errorf("failed to fetch PoX info: %w", err)
  214. }
  215. defer resp.Body.Close()
  216. if resp.StatusCode != http.StatusOK {
  217. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  218. }
  219. body, err := common.SafeRead(resp.Body)
  220. if err != nil {
  221. return nil, fmt.Errorf("failed to read response body: %w", err)
  222. }
  223. var poxInfo StacksV2PoxResponse
  224. if err := json.Unmarshal(body, &poxInfo); err != nil {
  225. return nil, fmt.Errorf("failed to parse PoX info response: %w", err)
  226. }
  227. return &poxInfo, nil
  228. }
  229. // Fetches node information from the Stacks node
  230. func (w *Watcher) fetchNodeInfo(ctx context.Context) (*StacksV2InfoResponse, error) {
  231. url := fmt.Sprintf("%s/v2/info", w.rpcURL)
  232. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  233. if err != nil {
  234. return nil, fmt.Errorf("failed to create request: %w", err)
  235. }
  236. resp, err := http.DefaultClient.Do(req)
  237. if err != nil {
  238. return nil, fmt.Errorf("failed to fetch Stacks node info: %w", err)
  239. }
  240. defer resp.Body.Close()
  241. if resp.StatusCode != http.StatusOK {
  242. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  243. }
  244. body, err := common.SafeRead(resp.Body)
  245. if err != nil {
  246. return nil, fmt.Errorf("failed to read response body: %w", err)
  247. }
  248. var nodeInfo StacksV2InfoResponse
  249. if err := json.Unmarshal(body, &nodeInfo); err != nil {
  250. return nil, fmt.Errorf("failed to parse Stacks node info response: %w", err)
  251. }
  252. return &nodeInfo, nil
  253. }