Die folgenden Fragen/Antworten ( Welchen Datentyp sollte ich für einen IPFS-Adress-Hash verwenden? ) empfehlen uns, bytes
ihn zum Speichern von IPFS-Hash zu verwenden.
Ich habe das folgende Beispiel ( https://github.com/AdrianClv/ethereum-ipfs/blob/master/NotSoSimpleStorage.sol ) verwendet, in dem string
IPFS-Hash gespeichert wird, der etwa 110.000 Gaspreis kostet, was ziemlich teuer erscheint.
[F] Ist die Nutzung bytes
statt von string
zum Speichern von IPFS-Hash günstiger? Ich beobachte, dass das Speichern bytes
statt der string
Kosten sehr nahe bei string
(110.000 Gas) liegt. Da die Speicherung beider Datentypen teuer erscheint, sollte ich Ereignisse verwenden, um sie zu speichern?
Gibt es ein Beispiel/Tutorial zum Speichern von IPFS-Hash mit bytes
?
Würde das funktionieren:
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.
}
}
Ihr Beispiel zeigt das Speichern einer IPFS-Identität mit ihrer alphanumerischen Codierung ( Qm...
), die dieselbe Base58 - Codierung ist, die Bitcoin verwendet. Was es jedoch im Kern darstellt, ist eine Zahl (der Hash). Das Speichern der Kennung im Base58-Format muss eine Zeichenfolge sein, da sie Buchstaben enthält (und was tatsächlich gespeichert wird, ist der ASCII-Code für jedes alphanumerische Zeichen in der Kennung). QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
Das bedeutet, dass Sie aus Ihrem Beispiel 46 Bytes zum Speichern benötigen .
Dieser Bezeichner kann jedoch auch hexadezimal als 12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89
, das nur 34 Byte lang ist, ausgedrückt werden (es werden 68 Zeichen benötigt, um es hexadezimal auszugeben, da alle zwei Zeichen in Hex ein Datenbyte sind).
Aber beide sind größer als 32 Bytes, was das maximale Byte-Array mit fester Größe ist, also müssen sie ein Byte-Array mit dynamischer Größe zum Speichern verwenden ( bytes
oder string
, die beide teuer sind, wie Sie bemerkt haben ).
ABER , dieser IPFS-Hash besteht eigentlich aus zwei verketteten Teilen. Es handelt sich um eine Multihash - Kennung, sodass die ersten beiden Bytes die verwendete Hash-Funktion und die Größe angeben. 0x12
ist sha2, 0x20
ist 256 Bit lang. Derzeit ist dies das einzige Format, das IPFS verwendet, sodass Sie einfach die ersten beiden Bytes abschneiden könnten, wodurch Sie einen 32-Byte-Wert erhalten, der klein genug ist, um in ein bytes32
Byte-Array mit fester Größe zu passen, und Sie dort etwas Platz sparen (und beim Abrufen kann entweder Ihr Vertrag wieder 0x1220
vorne angehängt werden, oder Ihre Kunden müssen schlau genug sein, dies zu tun, nachdem sie den Wert abgerufen haben).
Wenn Sie jedoch sicherstellen möchten, dass Ihr Code zukunftssicher ist, möchten Sie wahrscheinlich den Hash-Funktionscode und die Größe speichern, die Sie mit dem Hash als Struktur kombinieren könnten:
struct Multihash {
bytes32 hash
uint8 hash_function
uint8 size
}
Das funktioniert mit jedem Multihash-Format, solange size
es kleiner oder gleich 32 ist (je größer und die tatsächliche Nutzlast passt nicht in die hash
Eigenschaft). Diese Struktur benötigt zum Speichern zwei Speicherslots (zwei 32-Byte-Blöcke), da die beiden uint8
Teile in einen Slot gesteckt werden können. Sie können dieser Struktur auch bis zu 30 Byte zusätzliche Daten hinzufügen, ohne dass weitere Speicherkosten anfallen.
Hier sind einige js-Funktionen zum Entfernen und erneuten Hinzufügen der ersten beiden Bytes, die die Hash-Funktion und -Größe enthalten, die für web3 geeignet sind.
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
}
Hier ist die im Kontext verwendete Funktion, die einen Listing
mit Truffle bereitgestellten Vertrag aufruft.
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)
})
})
})
Entnommen aus meiner Arbeit an der Origin Demo Dapp hier: https://github.com/OriginProtocol/origin-js/blob/1cfc84d4693974bbf18e345e6c0def843321130c/src/services/contract-service.js#L102-L128
Ich habe eine ähnliche Situation mit dieser util-Funktion in web3.py behandelt:
import base58
def convertIpfsBytes32(hash_string):
bytes_array = base58.b58decode(hash_string)
return bytes_array[2:]
Sie benötigen das base58-Modul. Das Konzept ist dasselbe wie die akzeptierte Antwort.
Diese Antwort ist nur die Implementierung der oben akzeptierten AntwortPython
von @ MidnightLightning . Ich habe Web3.py verwendet .
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
Hier ist ein vollständigeres Beispiel mit der js-multihash- Bibliothek:
MeinVertrag.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)
})
})
Auslesen der Daten
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()
})
Alle Antworten hier sind unnötig kompliziert. Wenn Sie am Ende sowieso 64 Bytes speichern wollen, warum speichern Sie nicht einfach die ersten 32 Bytes des IPFS-Hashs in einem string32
und den Rest des Hashs in einem anderen string32
? Kein Umrüsten beim Lagern nötig, daher weniger Gas zu verarbeiten. Einfach verketten, um den Hash wiederherzustellen, daher billiger zu verarbeiten. Gleiche Gaskosten zum Speichern (64 Byte). Und zukunftssicherer . Anders als bei der ausgewählten Lösung wächst (oder schrumpft) die Länge der IPFS-Hash-Änderung, ohne dass eine Änderung am Code erforderlich ist.
bytes32
, warum sollten Sie es dann in 2 speichern bytes32
? In Ihrem Vertrag müssen Sie alles für 2 Slots von konvertieren bytes32
, wenn Sie diese beiden in einen schieben, list
wird es zusätzliche geben, length
so dass unnötiger Speicher verbraucht wird. "Same gas cost to store (64 bytes)" => das ist falsch, das Speichern 32 bytes
ist billiger als das Speichern von 64 Bytes in Fällen von tight variable packing
siehe fravoll.github.io/solidity-patterns/tight_variable_packing.html . Auch wenn Sie es in einem Steckplatz aufbewahren, können Sie es als Eingang key
verwendenmap
Wenn Sie keine Zeit haben, den Algorithmus zu recherchieren, empfehle ich die Verwendung des content-hash
Knotenmoduls. Sie können viele Formate von Zeichenfolgen einfach konvertieren, z. B.: CID v0
, CID v1
, base58
, base32
.
Beispiel:
const ipfs = 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG'
const cidV1 = contentHash.helpers.cidV0ToV1Base32(ipfs)
// 'bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4'
Alper
12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89
. Hier ( codebeautify.org/string-hex-converter ) wenn ichQmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
in hex konvertiere, bekomme ich eine viel größere Zeichenfolge als516d576d796f4d6f63746662416169457332473436677065556d687146524457364b576f3634793572353831567a
. @MidnightLightningAlper
uint8 function
, warum brauchen wir es und wo könnten wir es verwenden? Vielen Dank. @MidnightLightningMitternachtBlitz
0x51
, "m" ist0x6d
usw.). Was ich getan habe, ist ein Tool zu verwenden, das Base58-Decodierung durchführt und das verwendet, um die tatsächliche Zahl zu erhalten, die durch diese Base58-Zeichenfolge dargestellt wird.MitternachtBlitz
uint8 function
speichert einen vorzeichenlosen ganzzahligen Wert als Namen "Funktion" in diesem Strukturobjekt. "Funktion" ist wahrscheinlich nicht der beste Name dafür, da es sich um ein spezielles Wort in Solidity und anderen Programmiersprachen handelt; Ich habe es ausgewählt, weil sie im Multihash-Standard diese Variable so nennen; die Variable, die Ihnen mitteilt, welche Hash-Funktion für diesen bestimmten Datensatz verwendet wurde (z . B.0x12
für "sha2"). Ich werde meine Antwort aktualisieren, um das spezielle Wort "Funktion" nicht zu verwenden, um es klarer zu machen.Alper
rhlsthrm
Saurfang
Thomas JayRush
Peter