github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/manifest/download.go (about)

     1  // Copyright 2021 The TrueBlocks Authors. All rights reserved.
     2  // Use of this source code is governed by a license that can
     3  // be found in the LICENSE file.
     4  
     5  package manifest
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"path"
    14  	"strings"
    15  
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/abi"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/articulate"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/call"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    21  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/debug"
    22  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    23  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    24  	ethAbi "github.com/ethereum/go-ethereum/accounts/abi"
    25  )
    26  
    27  // ReadUnchainedIndex calls UnchainedIndex smart contract to get the current manifest IPFS CID as
    28  // published by the given publisher
    29  func ReadUnchainedIndex(chain string, publisher base.Address, database string) (string, error) {
    30  	cid := os.Getenv("TB_OVERRIDE_CID")
    31  	if cid != "" {
    32  		return cid, nil
    33  	}
    34  
    35  	callAddress, abiMap, err := getUnchainedAbi()
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  
    40  	unchainedChain := "mainnet" // the unchained index is on mainnet
    41  	conn := rpc.TempConnection(unchainedChain)
    42  	// if conn.LatestBlockTimestamp < 1_705_173_443 { // block 19_000_000
    43  	// 	provider := config.GetChain(unchainedChain).RpcProvider
    44  	// 	logger.Fatal(usage.Usage(unchainedWarning, provider))
    45  	// }
    46  
    47  	theCall := fmt.Sprintf("manifestHashMap(%s, \"%s\")", publisher, database)
    48  	if contractCall, _, err := call.NewContractCallWithAbi(conn, callAddress, theCall, abiMap); err != nil {
    49  		wrapped := fmt.Errorf("the --call value provided (%s) was not found: %s", theCall, err)
    50  		return "", wrapped
    51  	} else {
    52  		contractCall.BlockNumber = conn.GetLatestBlockNumber()
    53  		artFunc := func(str string, function *types.Function) error {
    54  			return articulate.ArticulateFunction(function, "", str[2:])
    55  		}
    56  		if result, err := contractCall.Call(artFunc); err != nil {
    57  			return "", err
    58  		} else {
    59  			return result.Values["hash"], nil
    60  		}
    61  	}
    62  }
    63  
    64  // downloadManifest downloads manifest from the given gateway and parses it into
    65  // Manifest struct. Both JSON and TSV formats are supported, but the server has
    66  // to set the correct Content-Type header.
    67  func downloadManifest(chain, gatewayUrl, cid string) (*Manifest, error) {
    68  	_ = chain // linter
    69  	url, err := url.Parse(gatewayUrl)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	url.Path = path.Join(url.Path, cid)
    74  
    75  	debug.DebugCurlStr(url.String())
    76  	resp, err := http.Get(url.String())
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if resp.StatusCode != http.StatusOK {
    82  		return nil, fmt.Errorf("fetch to pinning service (%s) failed: %s", url.String(), resp.Status)
    83  	}
    84  
    85  	switch resp.Header.Get("Content-Type") {
    86  	case "application/json":
    87  		m := &Manifest{}
    88  		err := json.NewDecoder(resp.Body).Decode(m)
    89  		return m, err
    90  	default:
    91  		return nil, fmt.Errorf("fetch to %s return unrecognized content type: %s", url.String(), resp.Header.Get("Content-Type"))
    92  	}
    93  }
    94  
    95  func getUnchainedAbi() (base.Address, *abi.SelectorSyncMap, error) {
    96  	abiMap := &abi.SelectorSyncMap{}
    97  	callAddress := base.HexToAddress(config.GetUnchained().SmartContract)
    98  
    99  	var unchainedAbiJson = `[
   100    {
   101      "name": "manifestHashMap",
   102      "type": "function",
   103      "signature": "manifestHashMap(address,string)",
   104      "encoding": "0x7087e4bd",
   105      "inputs": [
   106        {
   107          "type": "address",
   108          "name": "publisher",
   109          "internalType": "address"
   110        },
   111        {
   112          "type": "string",
   113          "name": "database",
   114          "internalType": "string"
   115        }
   116      ],
   117      "outputs": [
   118        {
   119          "type": "string",
   120          "name": "hash",
   121          "internalType": "string"
   122        }
   123      ]
   124    }
   125  ]`
   126  
   127  	if abi, err := ethAbi.JSON(strings.NewReader(unchainedAbiJson)); err != nil {
   128  		return base.Address{}, abiMap, err
   129  	} else {
   130  		method := abi.Methods["manifestHashMap"]
   131  		function := types.FunctionFromAbiMethod(&method)
   132  		abiMap.SetValue(function.Encoding, function)
   133  	}
   134  
   135  	return callAddress, abiMap, nil
   136  }
   137  
   138  // var unchainedWarning string = `
   139  // The Unchained Index requires your mainnet RPC to be synced (at least to block 0x1304073 or 19000000).
   140  // Check the progress with the following curl command and try again later.
   141  
   142  // curl -X POST -H "Content-Type: application/json" --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' {0}
   143  // `