如何使用 Solidity 和 Web.js 在以太坊区块链上保存和检索数据
How to save and retrieve data on Ethereum blockchain with Solidity and Web.js
下面的代码只是 return 一张收据,但我希望它是 return 一个数据元组,就像下面的合同一样。我如何获取它到 return 的数据?我找不到关于如何保存和检索数据的好教程。我知道这是一个昂贵的用例,我只是想做一个基本的概念证明并同时学习。
我正在使用 web3@1.0.0-beta.29
export class AppComponent {
title = 'app';
dappUrl: string = 'http://myapp.com';
web3: any;
contractHash: string = '0x3b8a60616bde6f6d251e807695900f31ab12ce1a';
MyContract: any;
contract: any;
ABI: any = [{"constant":true,"inputs":[{"name":"idx","type":"uint256"}],"name":"getLocationHistory","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"recentLocation","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"name":"saveLocation","outputs":[],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"getLastLocation","outputs":[{"components":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"name":"recentLocation","type":"tuple"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"locations","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"item","outputs":[{"name":"id","type":"bytes32"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"id","type":"bytes32"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}];
constructor(private route: ActivatedRoute) { }
@HostListener('window:load')
windowLoaded() {
this.checkAndInstantiateWeb3();
this.getLocation();
}
getLocationHistory() {
this.MyContract.methods
.getLocationHistory(0).send({
'from': '0x902D578B7E7866FaE71b3AB0354C9606631bCe03',
'gas': '44000'
}).then((result) => {
this.MyContract.methods.getLocationHistory(0).call()
.then(hello => {console.log('hello', hello)});
});
}
private checkAndInstantiateWeb3 = () => {
if (typeof window.web3 !== 'undefined') {
console.warn('Using web3 detected from external source.');
// Use Mist/MetaMask's provider
this.web3 = new Web3(window.web3.currentProvider);
} else {
console.warn(`No web3 detected. Falling back to http://localhost:8545.`);
this.web3 = new Web3(
new Web3.providers.HttpProvider('http://localhost:8545')
);
}
this.MyContract = new this.web3.eth.Contract(this.ABI, this.contractHash);
}
private getLocation(): void {
let query = this.route.snapshot.queryParams;
if (query.action && query.action === 'setLocation') {
this.setLocation();
}
}
private setLocation(): void {
navigator.geolocation.getCurrentPosition((position) => {
this.MyContract.methods.saveLocation(
position.coords.longitude, position.coords.latitude, window.web3.fromAscii("test")
).send({'from': '0x902D578B7E7866FaE71b3AB0354C9606631bCe03'}
).then((result) => {
console.log('saveLocation')
console.log(result)
});
this.getLocationHistory();
});
}
}
Solidity 合约
pragma solidity ^0.4.11;
/// @title QRCodeTracking with delegation.
contract QRCodeTracking {
struct Location {
address delegate;
uint128 longitude;
uint128 latitude;
bytes32 name;
}
struct Item {
bytes32 id;
bytes32 name;
}
Item public item;
Location[] public locations;
Location public recentLocation;
function QRCodeTracking(bytes32 id, bytes32 name) public {
// Limit gas
locations.length = 100;
item = Item({id: id, name: name});
}
function saveLocation (
uint128 longitude,
uint128 latitude,
bytes32 name
) public constant {
locations.push(Location({
delegate: msg.sender,
longitude: longitude,
latitude: latitude,
name: name
}));
}
function getLocationHistory(uint idx) constant
returns (address delegate, uint128 longitude, uint128 latitude, bytes32 name) {
Location storage loc = locations[idx];
return (loc.delegate, loc.longitude, loc.latitude, loc.name);
}
function getLastLocation() public
returns (Location recentLocation) {
recentLocation = locations[locations.length - 1];
return recentLocation;
}
}
您的代码存在一些问题,主要集中在 constant
函数的使用和理解 web3 中 send
和 call
之间的区别。
在 Solidity 中,constant
(或 view
)修饰符用于标记不改变状态的合约函数(参见 docs)。如果您尝试在 constant
函数中更新合约状态,状态更改将不会持续存在。因此,在您的 Solidity 合约中,saveLocation
不应该是 constant
。但是,getLastLocation
和 getLocationHistory
都只从状态中读取,所以它们都应该是 constant
.
在客户端,使用web3调用合约函数也有同样的区别。当你想在区块链上执行交易时使用 send
,但当你想从合约中检索数据时使用 call
。
当您使用 this.MyContract.methods.getLocationHistory(0).send()
时,您正在针对您的合约执行交易,交易收据通过传入的回调(或通过 .then
Promise)发回。可以找到发送的 Web3 文档 here. Important note - nothing from the contract state can be returned from a Solidity function that executes a transaction. If you have returns
in a non-constant function, it will compile, but the data is not returned. The only way to return data from the contract is to use constant
functions or by using events. For your use case, you want to use this.MyContract.methods.getLocationHistory(0).call()
. Call web3 documentation.
编辑 - 添加简化的测试客户端。注意 send
与 call
.
的使用
const Web3 = require('web3');
const solc = require('solc');
const fs = require('fs');
const provider = new Web3.providers.HttpProvider("http://localhost:8545")
const web3 = new Web3(provider);
web3.eth.getAccounts().then((accounts) => {
const code = fs.readFileSync('./QRCodeTracking.sol').toString();
const compiledCode = solc.compile(code);
const byteCode = compiledCode.contracts[':QRCodeTracking'].bytecode;
// console.log('byteCode', byteCode);
const abiDefinition = JSON.parse(compiledCode.contracts[':QRCodeTracking'].interface);
const deployTransactionObject = {
data: byteCode,
from: accounts[0],
gas: 4700000
};
let deployedContract;
const MyContract = new web3.eth.Contract(abiDefinition, deployTransactionObject);
MyContract.deploy({arguments: [web3.utils.asciiToHex("someId"), web3.utils.asciiToHex("someName")]}).send((err, hash) => {
if (err)
console.log("Error: " + err);
else
console.log("TX Hash: " + hash);
}).then(result => {
deployedContract = result;
deployedContract.setProvider(provider);
return deployedContract.methods.saveLocation(123456789, 987654321, web3.utils.asciiToHex("newLocationName")).send();
}).then(saveResult => {
return deployedContract.methods.getLocationHistory(0).call();
}).then(locationResult => {
console.log(locationResult);
})
});
下面的代码只是 return 一张收据,但我希望它是 return 一个数据元组,就像下面的合同一样。我如何获取它到 return 的数据?我找不到关于如何保存和检索数据的好教程。我知道这是一个昂贵的用例,我只是想做一个基本的概念证明并同时学习。
我正在使用 web3@1.0.0-beta.29
export class AppComponent {
title = 'app';
dappUrl: string = 'http://myapp.com';
web3: any;
contractHash: string = '0x3b8a60616bde6f6d251e807695900f31ab12ce1a';
MyContract: any;
contract: any;
ABI: any = [{"constant":true,"inputs":[{"name":"idx","type":"uint256"}],"name":"getLocationHistory","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"recentLocation","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"name":"saveLocation","outputs":[],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"getLastLocation","outputs":[{"components":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"name":"recentLocation","type":"tuple"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"locations","outputs":[{"name":"delegate","type":"address"},{"name":"longitude","type":"uint128"},{"name":"latitude","type":"uint128"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"item","outputs":[{"name":"id","type":"bytes32"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"id","type":"bytes32"},{"name":"name","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}];
constructor(private route: ActivatedRoute) { }
@HostListener('window:load')
windowLoaded() {
this.checkAndInstantiateWeb3();
this.getLocation();
}
getLocationHistory() {
this.MyContract.methods
.getLocationHistory(0).send({
'from': '0x902D578B7E7866FaE71b3AB0354C9606631bCe03',
'gas': '44000'
}).then((result) => {
this.MyContract.methods.getLocationHistory(0).call()
.then(hello => {console.log('hello', hello)});
});
}
private checkAndInstantiateWeb3 = () => {
if (typeof window.web3 !== 'undefined') {
console.warn('Using web3 detected from external source.');
// Use Mist/MetaMask's provider
this.web3 = new Web3(window.web3.currentProvider);
} else {
console.warn(`No web3 detected. Falling back to http://localhost:8545.`);
this.web3 = new Web3(
new Web3.providers.HttpProvider('http://localhost:8545')
);
}
this.MyContract = new this.web3.eth.Contract(this.ABI, this.contractHash);
}
private getLocation(): void {
let query = this.route.snapshot.queryParams;
if (query.action && query.action === 'setLocation') {
this.setLocation();
}
}
private setLocation(): void {
navigator.geolocation.getCurrentPosition((position) => {
this.MyContract.methods.saveLocation(
position.coords.longitude, position.coords.latitude, window.web3.fromAscii("test")
).send({'from': '0x902D578B7E7866FaE71b3AB0354C9606631bCe03'}
).then((result) => {
console.log('saveLocation')
console.log(result)
});
this.getLocationHistory();
});
}
}
Solidity 合约
pragma solidity ^0.4.11;
/// @title QRCodeTracking with delegation.
contract QRCodeTracking {
struct Location {
address delegate;
uint128 longitude;
uint128 latitude;
bytes32 name;
}
struct Item {
bytes32 id;
bytes32 name;
}
Item public item;
Location[] public locations;
Location public recentLocation;
function QRCodeTracking(bytes32 id, bytes32 name) public {
// Limit gas
locations.length = 100;
item = Item({id: id, name: name});
}
function saveLocation (
uint128 longitude,
uint128 latitude,
bytes32 name
) public constant {
locations.push(Location({
delegate: msg.sender,
longitude: longitude,
latitude: latitude,
name: name
}));
}
function getLocationHistory(uint idx) constant
returns (address delegate, uint128 longitude, uint128 latitude, bytes32 name) {
Location storage loc = locations[idx];
return (loc.delegate, loc.longitude, loc.latitude, loc.name);
}
function getLastLocation() public
returns (Location recentLocation) {
recentLocation = locations[locations.length - 1];
return recentLocation;
}
}
您的代码存在一些问题,主要集中在 constant
函数的使用和理解 web3 中 send
和 call
之间的区别。
在 Solidity 中,constant
(或 view
)修饰符用于标记不改变状态的合约函数(参见 docs)。如果您尝试在 constant
函数中更新合约状态,状态更改将不会持续存在。因此,在您的 Solidity 合约中,saveLocation
不应该是 constant
。但是,getLastLocation
和 getLocationHistory
都只从状态中读取,所以它们都应该是 constant
.
在客户端,使用web3调用合约函数也有同样的区别。当你想在区块链上执行交易时使用 send
,但当你想从合约中检索数据时使用 call
。
当您使用 this.MyContract.methods.getLocationHistory(0).send()
时,您正在针对您的合约执行交易,交易收据通过传入的回调(或通过 .then
Promise)发回。可以找到发送的 Web3 文档 here. Important note - nothing from the contract state can be returned from a Solidity function that executes a transaction. If you have returns
in a non-constant function, it will compile, but the data is not returned. The only way to return data from the contract is to use constant
functions or by using events. For your use case, you want to use this.MyContract.methods.getLocationHistory(0).call()
. Call web3 documentation.
编辑 - 添加简化的测试客户端。注意 send
与 call
.
const Web3 = require('web3');
const solc = require('solc');
const fs = require('fs');
const provider = new Web3.providers.HttpProvider("http://localhost:8545")
const web3 = new Web3(provider);
web3.eth.getAccounts().then((accounts) => {
const code = fs.readFileSync('./QRCodeTracking.sol').toString();
const compiledCode = solc.compile(code);
const byteCode = compiledCode.contracts[':QRCodeTracking'].bytecode;
// console.log('byteCode', byteCode);
const abiDefinition = JSON.parse(compiledCode.contracts[':QRCodeTracking'].interface);
const deployTransactionObject = {
data: byteCode,
from: accounts[0],
gas: 4700000
};
let deployedContract;
const MyContract = new web3.eth.Contract(abiDefinition, deployTransactionObject);
MyContract.deploy({arguments: [web3.utils.asciiToHex("someId"), web3.utils.asciiToHex("someName")]}).send((err, hash) => {
if (err)
console.log("Error: " + err);
else
console.log("TX Hash: " + hash);
}).then(result => {
deployedContract = result;
deployedContract.setProvider(provider);
return deployedContract.methods.saveLocation(123456789, 987654321, web3.utils.asciiToHex("newLocationName")).send();
}).then(saveResult => {
return deployedContract.methods.getLocationHistory(0).call();
}).then(locationResult => {
console.log(locationResult);
})
});