После вопросов и ответов ( какой тип данных следует использовать для хэша IPFS-адреса? ) рекомендуется использовать bytes
его для хранения хэша IPFS.
Я использовал следующий пример ( https://github.com/AdrianClv/ethereum-ipfs/blob/master/NotSoSimpleStorage.sol ), который используется string
для хранения хэша IPFS, который стоит около 110 000 газа, что кажется довольно дорогим.
[В] Обходится ли использование bytes
вместо string
того, чтобы хранить хеш IPFS дешевле? Я наблюдаю, что хранение bytes
вместо string
стоит очень близко к string
(110 000 газа). Поскольку хранение обоих типов данных кажется дорогим, должен ли я использовать события для их хранения?
Есть ли какой-нибудь пример/учебник, связанный с хранением хеша IPFS с использованием bytes
?
Будет ли это работать:
myContract.insertHash("QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz");
contract Example_bytes {
bytes[] list;
function insertHash(bytes ipfsHash) {
list.push(ipfsHash); //costs around 110,000 gas.
}
}
contract Example_string {
struct hashes{
string hash;
}
hashes[] list;
function insertHash(string ipfsHash) {
list.push(hashes{hash: ipfsHash); //costs around 110,000 gas.
}
}
В вашем примере показано сохранение идентификатора IPFS с использованием его буквенно-цифровой кодировки ( Qm...
), которая является той же кодировкой Base58 , которую использует биткойн. Однако по своей сути он представляет собой число (хэш). Сохранение идентификатора в формате Base58 должно быть строкой, поскольку она включает буквы (и фактически сохраняется код ASCII для каждого буквенно-цифрового символа в идентификаторе). Это означает, что вам нужно 46 байт для хранения QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
из вашего примера.
Однако этот идентификатор также может быть выражен в шестнадцатеричном виде как 12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89
, длина которого составляет всего 34 байта (для записи в шестнадцатеричном формате требуется 68 символов, поскольку каждые два символа в шестнадцатеричном формате представляют собой байт данных).
Но оба они больше 32 байтов, что является максимальным массивом байтов фиксированного размера, поэтому им потребуется использовать массив байтов динамического размера для хранения ( bytes
или string
оба из которых дороги, как вы заметили ).
НО , этот хэш IPFS на самом деле представляет собой две соединенные части. Это мультихэш- идентификатор, поэтому первые два байта указывают используемую хеш-функцию и размер. 0x12
это sha2, 0x20
имеет длину 256 бит. В настоящее время это единственный формат, который использует IPFS, поэтому вы можете просто отрезать первые два байта, что оставит вам 32-байтовое значение, достаточно маленькое, чтобы поместиться в bytes32
массив байтов фиксированного размера, и вы сэкономите там немного места . (и при извлечении либо ваш контракт может быть повторно присоединен 0x1220
к нему, либо ваши клиенты должны быть достаточно умными, чтобы сделать это после извлечения значения).
Однако, если вы хотите убедиться, что ваш код рассчитан на будущее, вы, вероятно, захотите сохранить код и размер хэш-функции, которые вы можете объединить с хэшем в виде структуры:
struct Multihash {
bytes32 hash
uint8 hash_function
uint8 size
}
Это будет работать с любым мультихеш-форматом, если size
он меньше или равен 32 (больше и фактическая полезная нагрузка не будет соответствовать hash
свойству). Эта структура займет два слота хранения (два фрагмента по 32 байта) для хранения, так как две uint8
части могут быть помещены в один слот. Вы также можете добавить до 30 байтов дополнительных данных в эту структуру без дополнительных затрат на хранение.
Вот несколько js-функций для удаления и повторного добавления первых двух байтов, содержащих хеш-функцию и размер, подходящих для web3.
import bs58 from 'bs58'
// Return bytes32 hex string from base58 encoded ipfs hash,
// stripping leading 2 bytes from 34 byte IPFS hash
// Assume IPFS defaults: function:0x12=sha2, size:0x20=256 bits
// E.g. "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL" -->
// "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
getBytes32FromIpfsHash(ipfsListing) {
return "0x"+bs58.decode(ipfsListing).slice(2).toString('hex')
}
// Return base58 encoded ipfs hash from bytes32 hex string,
// E.g. "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
// --> "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL"
getIpfsHashFromBytes32(bytes32Hex) {
// Add our default ipfs values for first 2 bytes:
// function:0x12=sha2, size:0x20=256 bits
// and cut off leading "0x"
const hashHex = "1220" + bytes32Hex.slice(2)
const hashBytes = Buffer.from(hashHex, 'hex');
const hashStr = bs58.encode(hashBytes)
return hashStr
}
Вот функция, используемая в контексте, вызывающая Listing
контракт, развернутый с помощью Truffle.
submitListing(ipfsListing, ethPrice, units) {
return new Promise((resolve, reject) => {
this.listingContract.setProvider(window.web3.currentProvider)
window.web3.eth.getAccounts((error, accounts) => {
this.listingContract.deployed().then((instance) => {
let weiToGive = window.web3.toWei(ethPrice, 'ether')
return instance.create(
this.getBytes32FromIpfsHash(ipfsListing), /*** IPFS here ***/
weiToGive,
units,
{from: accounts[0]})
}).then((result) => {
resolve(result)
}).catch((error) => {
console.error("Error submitting to the Ethereum blockchain: " + error)
reject(error)
})
})
})
Взято из моей работы над демо-приложением Origin здесь: https://github.com/OriginProtocol/origin-js/blob/1cfc84d4693974bbf18e345e6c0def843321130c/src/services/contract-service.js#L102-L128
Я обработал аналогичную ситуацию с этой функцией util в web3.py:
import base58
def convertIpfsBytes32(hash_string):
bytes_array = base58.b58decode(hash_string)
return bytes_array[2:]
Вам нужен модуль base58. Концепция такая же, как и принятый ответ.
Этот ответ является просто Python
реализацией принятого ответа @MidnightLightning выше . Я использовал Web3.py.
from web3.auto import w3
def _ipfs_to_bytes32(hash_str: str):
"""Ipfs hash is converted into bytes32 format."""
bytes_array = base58.b58decode(hash_str)
b = bytes_array[2:]
return binascii.hexlify(b).decode("utf-8")
def ipfs_to_bytes32(ipfs_hash: str) -> str:
"""bytes32 is converted back into Ipfs hash format."""
ipfs_hash_bytes32 = _ipfs_to_bytes32(ipfs_hash)
return w3.toBytes(hexstr=ipfs_hash_bytes32)
def bytes32_to_ipfs(bytes_array):
"""Convert bytes_array into IPFS hash format."""
merge = Qm + bytes_array
return base58.b58encode(merge).decode("utf-8")
if __name__ == "__main__":
ipfs_hash = "QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vd"
ipfs_bytes32 = ipfs_to_bytes32(ipfs_hash)
_ipfs_hash = bytes32_to_ipfs(ipfs_bytes32)
assert ipfs_hash == _ipfs_hash # They should be equal to each other
Вот более полный пример с использованием библиотеки js-multihash :
MyContract.sol
pragma solidity ^0.4.24;
contract MyContract {
event AddFile(address indexed owner, bytes32 digest, bytes2 hashFunction, uint8 size, bytes4 storageEngine);
function addFile(bytes32 _digest, bytes2 _hashFunction, uint8 _size, bytes4 _storageEnginge) public {
emit AddFile(msg.sender, _digest, _hashFunction, _size, _storageEngine);
}
}
Javascript
import Web3 from 'web3'
import multihashes from 'multihashes'
import ipfsAPI from 'ipfs-api'
var web3 = window.web3
web3 = new Web3(web3.currentProvider)
// Utility functions:
const utils = {
ipfs2multihash (hash) {
let mh = multihashes.fromB58String(Buffer.from(hash))
return {
hashFunction: '0x' + mh.slice(0, 2).toString('hex'),
digest: '0x' + mh.slice(2).toString('hex'),
size: mh.length - 2
}
},
multihash2hash (hashFunction, digest, size, storageEngine) {
storageEngine = web3.toAscii(storageEngine)
if (storageEngine === 'ipfs') {
hashFunction = hashFunction.substr(2)
digest = digest.substr(2)
return {
hash: multihashes.toB58String(multihashes.fromHexString(hashFunction + digest)),
engine: storageEngine
}
}
throw new Error('Unknown storage engine:', storageEngine)
}
}
// ... code to instantiate contract
// ... code to get the file buffer
ipfs.add(buffer)
.then((response) => {
console.log('ipfs hash:', response[0].hash)
// Prepare data
let mh = utils.ipfs2multihash(response[0].hash)
let storageEnginge = web3.fromAscii('ipfs')
// Call contract
myContractInstance.addFile.sendTransaction(mh.digest, mh.hashFunction, mh.size, storageEnginge, {from: myAccount, gas: 1000000}, (error, result) => {
if (error) throw error
console.log(result)
})
})
Чтение данных
let fileFilter = myContract.AddFile({
owner: myAccount
}, {
fromBlock: 0,
toBlock: 'latest'
}).watch((error, log) => {
if (error) reject(error)
console.log('file log:', log)
let hash = utils.multihash2hash(log.args.hashFunction, log.args.digest, log.args.size, log.args.storageEngine)
console.log('Hash:', hash)
fileFilter.stopWatching()
})
Все ответы здесь излишне сложны. Если, в конце концов, вы все равно собираетесь хранить 64 байта, почему бы просто не сохранить первые 32 байта хэша IPFS в одном string32
, а оставшуюся часть хэша в другом string32
? Нет необходимости конвертировать при хранении, поэтому требуется перерабатывать меньше газа. Просто объедините, чтобы восстановить хэш, поэтому его обработка дешевле. Та же стоимость газа для хранения (64 байта). И, больше будущего доказательства. В отличие от выбранного решения, длина изменения хэша IPFS увеличивается (или уменьшается) без необходимости изменения кода.
bytes32
, зачем хранить ее в двух bytes32
? В вашем контракте вы должны преобразовать все для 2 слотов bytes32
, если вы вставите эти два в , list
будет дополнительная length
память, поэтому будет потребляться ненужная память. «Та же стоимость газа для хранения (64 байта)» => это неправильно, хранение 32 bytes
дешевле, чем хранение 64 байтов в случаях, tight variable packing
пожалуйста, см . fravoll.github.io/solidity-patterns/tight_variable_packing.html . также, когда вы храните его в одном слоте, вы можете использовать его как key
входнойmap
Если у вас нет времени изучать алгоритм, я предлагаю использовать content-hash
модуль узла. Вы можете легко преобразовать многие форматы строк, такие как: CID v0
, CID v1
, base58
, base32
.
пример:
const ipfs = 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG'
const cidV1 = contentHash.helpers.cidV0ToV1Base32(ipfs)
// 'bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4'
обратитесь к: https://github.com/ensdomains/content-hash
альпер
12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89
. Здесь ( codebeautify.org/string-hex-converter ), когда я конвертируюQmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
в шестнадцатеричный формат, я получаю гораздо большую строку, чем516d576d796f4d6f63746662416169457332473436677065556d687146524457364b576f3634793572353831567a
. @MidnightLightningальпер
uint8 function
зачем нам это нужно и где мы можем это использовать? Спасибо. @MidnightLightningПолночьМолния
0x51
, "m" равно и т. д0x6d
.). Что я сделал, так это использовал инструмент, который выполняет декодирование Base58, и использовал его, чтобы получить фактическое число, представленное этой строкой Base58.ПолночьМолния
uint8 function
хранит целочисленное значение без знака в качестве имени «функция» в этом объекте структуры. «Функция», вероятно, не лучшее название для этого, поскольку это специальное слово в Solidity и других языках программирования; Я выбрал его, потому что в стандарте мультихеширования именно так называется эта переменная; переменная, которая сообщает вам, какая функция хеширования использовалась для этой конкретной записи (например0x12
, для "sha2"). Я обновлю свой ответ, чтобы не использовать это специальное слово «функция», чтобы быть более понятным.альпер
рхлстрм
Саурфанг
Томас Джей Раш
Питер