Blockchain 如何使用Solidity和Web.js在以太坊区块链上保存和检索数据

Blockchain 如何使用Solidity和Web.js在以太坊区块链上保存和检索数据,blockchain,ethereum,solidity,web3js,web3,Blockchain,Ethereum,Solidity,Web3js,Web3,下面的代码只返回收据,但我希望它返回一个数据元组,如下面的合同中所示。如何让它返回数据?我找不到关于如何保存和检索数据的好教程。我知道这是一个昂贵的用例,我只是想做一个基本的概念验证,同时学习 我正在使用web3@1.0.0-β.29 export class AppComponent { title = 'app'; dappUrl: string = 'http://myapp.com'; web3: any; contractHash: string = '0x3b8a60

下面的代码只返回收据,但我希望它返回一个数据元组,如下面的合同中所示。如何让它返回数据?我找不到关于如何保存和检索数据的好教程。我知道这是一个昂贵的用例,我只是想做一个基本的概念验证,同时学习

我正在使用web3@1.0.0-β.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();

    });
  }    

}
实体合同

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;
    }
}

您的代码中存在一些问题,这些问题的核心是如何使用
常量
函数,以及如何理解web3中
发送
调用
之间的区别

在Solidity中,
常量
(或
视图
)修饰符用于标记不改变状态的契约函数(请参阅)。如果试图在
常量
函数中更新合约状态,状态更改将不会持续。因此,在您的Solidity合同中,
saveLocation
不应是
常量
。但是,
getLastLocation
getLocationHistory
都只能从状态读取,因此它们都应该是
常量

在客户端,当使用web3调用契约函数时,您有相同的区别。当您想要在区块链上执行交易时,使用
send
,但当您想要从合同中检索数据时,使用
call

当您使用
this.MyContract.methods.getLocationHistory(0).send()
时,您正在根据合同执行一项交易,交易凭证将通过传入的回调(或通过
。然后
承诺)发回。可以找到用于发送的Web3文档。重要提示-执行事务的Solidity函数不能返回任何来自契约状态的内容。如果在非常量函数中有
返回值
,它将编译,但不会返回数据。从合同返回数据的唯一方法是使用
常量
函数或使用。对于您的用例,您希望使用
this.MyContract.methods.getLocationHistory(0.call()

编辑-添加简化的测试客户端。注意
send
vs
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);
  })
});

调用
this.MyContract.methods.saveLocation时出现问题?您是说.call()将返回数据吗。call()只在本地返回,不访问区块链那是不对的。它不会发布到区块链,但它在EVM中执行,并确实从中读取。如果您正在运行一个本地节点,那么调用将在本地运行,您不必支付所用的煤气费。好的,但它仍然没有返回我的数据,这是我最初的问题。刚刚注意到您的另一个错误:
locations.length=100正在
位置中创建100个元素
数组,所有元素都已初始化,字段设置为0。调用
saveLocation
时,您正在索引100处向数组添加一个元素。当您检索索引0处的元素时,您将获得一个全部为0的位置对象。拆下那条线。在做了那个更改(加上我提到的其他更改)之后,我测试了合同并运行了它。我用我自己的客户端版本更新了答案,更简单了一点。好的,非常感谢。我最初把它放进去是因为Remix IDE不会编译,并且找到了一个stackoverflow帖子,上面说要设置长度以避免Remix抛出的无限递归错误。很高兴我因为一个混音IDE错误失去了三天的工作。现在它在混音中工作没有问题。啊。。