Pull to refresh

Выполнение публичных и приватных транзакций в блокчейне JPMorgan Quorum с использованием Web3

Reading time8 min
Views3.6K
Original author: Lee Ruian, Thomas



Quorum — блокчейн на базе Ethereum, разработанный JPMorgan и совсем недавно ставший первой платформой распределенного реестра, которую предлагает Microsoft Azure.


Quorum поддерживает приватные и публичные транзакции и имеет много коммерческих сценариев использования.


В данной статье мы разберем один из таких сценариев — развертывание сети распределенного реестра между супермаркетом и владельцем склада для обеспечения актуальной информации о температуре складского помещения.


Код, используемый в данном руководстве, лежит в репозитории на GitHub.


Статья покрывает:


  • создание смарт-контракта;
  • развертывание сети Quorum при помощи Chainstack;
  • публичные транзакции Quorum;
  • приватные транзакции Quorum.

Для иллюстрации используется сценарий мониторинга температуры в складских помещениях участников сети Quorum в рамках Internet of Things (IoT).


Контекст


Группа складских компаний объединяется в консорциум для совместного хранения информации и автоматизации процессов на блокчейне. Для этого компании решили использовать Quorum. В данной статье мы покроем два сценария: публичные транзакции и приватные транзакции.


Транзакции создаются разными участниками для взаимодействия с консорциумом, которому они принадлежат. Каждая транзакция либо деплоит контракт, либо вызывает функцию в контракте для загрузки данных в сеть. Данные действия реплицируются на все ноды в сети.


Публичные транзакции доступны для просмотра всеми участниками консорциума. Приватные же транзакции добавляют слой конфиденциальности и доступны только тем участникам, у которых есть на это права.


Для обоих сценариев мы для наглядности используем один и тот же контракт.


Смарт-контракт


Ниже приведен простой смарт-контракт, созданный для нашего сценария. В нем есть публичная переменная temperature, которую можно изменять методом set и получать методом get.


pragma solidity ^0.4.25;
contract TemperatureMonitor {
  int8 public temperature;
function set(int8 temp) public {
    temperature = temp;
  }
function get() view public returns (int8) {
    return temperature;
  }
}

Для того, чтобы контракт работал с web3.js, его необходимо перевести в формат ABI и байткод. Использование функции formatContract, приведенной ниже, компилирует контракт при помощи solc-js.


function formatContract() {
  const path = './contracts/temperatureMonitor.sol';
  const source = fs.readFileSync(path,'UTF8');
return solc.compile(source, 1).contracts[':TemperatureMonitor'];
}

Готовый контракт выглядит следующим образом:


// interface
[ 
  { 
    constant: true,
    inputs: [],
    name: ‘get’,
    outputs: [Array],
    payable: false,
    stateMutability: ‘view’,
    type: ‘function’ 
  },
  { 
    constant: true,
    inputs: [],
    name: ‘temperature’,
    outputs: [Array],
    payable: false,
    stateMutability: ‘view’,
    type: ‘function’ 
  },
  {
    constant: false,
    inputs: [Array],
    name: ‘set’,
    outputs: [],
    payable: false,
    stateMutability: ‘nonpayable’,
    type: ‘function’ 
  }
]

// bytecode
0x608060405234801561001057600080fd5b50610104806100206000396000f30060806040526004361060525763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416636d4ce63c81146057578063adccea12146082578063faee13b9146094575b600080fd5b348015606257600080fd5b50606960ae565b60408051600092830b90920b8252519081900360200190f35b348015608d57600080fd5b50606960b7565b348015609f57600080fd5b5060ac60043560000b60c0565b005b60008054900b90565b60008054900b81565b6000805491810b60ff1660ff199092169190911790555600a165627a7a72305820af0086d55a9a4e6d52cb6b3967afd764ca89df91b2f42d7bf3b30098d222e5c50029

Теперь, когда контракт готов, мы развернем сеть и задеплоим контракт.


Развертывание нод


image


Развертывание ноды может быть довольно трудоемким и этот процесс можно заменить использованием сервиса Chainstack.


Ниже приведен процесс развертывания сети Quorum с консенсусом Raft и тремя нодами.


Для начала заведем проект и назовем его Quorum Project:


image


Создадим сеть Quorum с консенсусом Raft на Google Cloud Platform:


image


К уже созданной по умолчанию ноде добавим еще две ноды:


image


Три запущенные ноды:


image


Страница деталей ноды показывает RPC endpoint, публичный ключ и т. д.


image


Сеть развернута. Теперь займемся деплоем смарт-контрактов и выполнением транзакций при помощи web3.js.


Публичные транзакции


Контекст


Температура складского помещение имеет большое значение для снижения затрат, особенно для продуктов, предназначенных для хранения при минусовой температуре.


Давая компаниям возможность делиться значениями внешней температуры их географического положения в режиме реального времени и записывать в неизменяемый реестр, участники сети сокращают расходы и время.


image


Мы выполним три задачи, проиллюстрированные на диаграмме:


  1. Задеплоим контракт через Node 1:


    const contractAddress = await deployContract(raft1Node);
    console.log(`Contract address after deployment: ${contractAddress}`);

  2. Установим температуру через Node 2 на 3 градуса:


    const status = await setTemperature(raft2Node, contractAddress, 3);
    console.log(`Transaction status: ${status}`);

  3. Node 3 получит информацию из смарт-контракта. Контракт вернет значение 3 градуса:


    const temp = await getTemperature(raft3Node, contractAddress);
    console.log(‘Retrieved contract Temperature’, temp);

    Далее рассмотрим как исполнить публичную транзакцию в сети Quorum с использованием web3.js.



Инициируем инстанс через RPC для трех нод:


const raft1Node = new Web3(
 new Web3.providers.HttpProvider(process.env.RPC1), null, {
   transactionConfirmationBlocks: 1,
 },
);
const raft2Node = new Web3(
 new Web3.providers.HttpProvider(process.env.RPC2), null, {
   transactionConfirmationBlocks: 1,
 },
);
const raft3Node = new Web3(
 new Web3.providers.HttpProvider(process.env.RPC3), null, {
   transactionConfirmationBlocks: 1,
 },
);

Задеплоим смарт-контракт:


// returns the default account from the Web3 instance initiated previously
function getAddress(web3) {
  return web3.eth.getAccounts().then(accounts => accounts[0]);
}
// Deploys the contract using contract's interface and node's default address
async function deployContract(web3) {
  const address = await getAddress(web3);
// initiate contract with contract's interface
  const contract = new web3.eth.Contract(
    temperatureMonitor.interface
  );
return contract.deploy({
    // deploy contract with contract's bytecode
    data: temperatureMonitor.bytecode,
  })
  .send({
    from: address,
    gas: '0x2CD29C0',
  })
  .on('error', console.error)
  .then((newContractInstance) => {
    // returns deployed contract address
    return newContractInstance.options.address;
  });
}

web3.js предоставляет два метода для взаимодействия с контрактом: call и send.


Обновим температуру контракта выполнением set используя метод web3 send.


// get contract deployed previously
async function getContract(web3, contractAddress) {
  const address = await getAddress(web3);
return web3.eth.Contract(
    temperatureMonitor.interface,
    contractAddress, {
      defaultAccount: address,
    }
  );
}
// calls contract set method to update contract's temperature
async function setTemperature(web3, contractAddress, temp) {
  const myContract = await getContract(web3, contractAddress);
return myContract.methods.set(temp).send({}).then((receipt) => {
    return receipt.status;
  });
}

Далее используем метод web3 call для получения температуры контракта. Обратите внимание, что метод call выполняется на локальной ноде и на блокчейне транзакция создана не будет.


// calls contract get method to retrieve contract's temperature
async function getTemperature(web3, contractAddress) {
  const myContract = await getContract(web3, contractAddress);
return myContract.methods.get().call().then(result => result);
}

Теперь можно запустить public.js для получения следующего результата:


// Execute public script
node public.js
Contract address after deployment: 0xf46141Ac7D6D6E986eFb2321756b5d1e8a25008F
Transaction status: true
Retrieved contract Temperature 3

Далее мы можем посмотреть записи в эксплорере Quorum в панели Chainstack, как показано ниже.


Все три ноды провзаимодействовали и транзакции обновились:


  1. Первая транзакция задеплоила контракт.
  2. Вторая транзакция установила температуру контракта в 3 градуса.
  3. Получение температуры происходит через локальную ноду, поэтому транзакция не создана.

image


Приватные транзакции


Контекст


Частым требованием организаций является защита данных. В качестве примера рассмотрим сценарий, в котором Cупермаркет арендует складское помещение для хранения морепродуктов у отдельного Вендора:


  • Вендор при помощи IoT-датчиков считывает температурные значения каждые 30 секунд и передает их Cупермаркету;
  • данные значения должны быть доступны только Вендору и Супермаркету, объединенным в сеть консорциум.

image


Мы выполним четыре задачи, проиллюстрированные на диаграмме выше.


  • Используем те же три ноды из предыдущего сценария для демонстрации приватных транзакций:
  • Супермаркет деплоит смарт-контракт, который является приватным для Супермаркета и Вендора.
  • Третья сторона не имеет права доступа к смарт-контракту.

Мы вызовем методы get и set от лица Супермаркета и Вендора для демонстрации приватной транзакции Quorum.


  1. Задеплоим приватный контракт для участников Супермаркет и Вендор через участника Супермаркет:


    const contractAddress = await deployContract(
    raft1Node,
    process.env.PK2,
    );
    console.log(`Contract address after deployment: ${contractAddress}`);

  2. Установим температуру от Третьей стороны (внешняя нода) и получим значение температуры:


    // Attempts to set Contract temperature to 10, this will not mutate contract's temperature
    await setTemperature(
    raft3Node,
    contractAddress,
    process.env.PK1,
    10,
    );
    // This returns null
    const temp = await getTemperature(raft3Node, contractAddress);
    console.log(`[Node3] temp retrieved after updating contract from external nodes: ${temp}`);

  3. Установим температуру от Вендора (внутренняя нода) и получим значение температуры:


    Температура в данном сценарии должна вернуть из смарт-контракта значение 12. Обратите внимание, что Вендор здесь имеет разрешенный доступ к смарт-контракту.


    // Updated Contract temperature to 12 degrees
    await setTemperature(
    raft2Node,
    contractAddress,
    process.env.PK1,
    12,
    );
    // This returns 12
    const temp2 = await getTemperature(raft2Node, contractAddress);
    console.log(`[Node2] temp retrieved after updating contract from internal nodes: ${temp2}`);

  4. Получим температуру от Третьей стороны (внешняя нода):


    На шаге 3 температура была установлена со значением 12, но Третья сторона не имеет доступа к смарт-контракту. Поэтому возврат значения должен быть null.


    // This returns null
    const temp3 = await getTemperature(raft3Node, contractAddress);
    console.log(`[Node3] temp retrieved from external nodes after update ${temp}`);

    Далее более детально рассмотрим выполнение приватных транзакций в сети Quorum с web3.js. Поскольку большая часть кода совпадает с публичными транзакциями, выделим только те части, которые отличаются для приватных транзакций.



Обратите внимание, что контракт, загруженный в сеть, является неизменяемым, поэтому доступ с разрешением (permissioned) должен быть выдан соответствующим нодам путем включения публичного контракта на момент деплоя контракта, а не после.


async function deployContract(web3, publicKey) {
  const address = await getAddress(web3);
  const contract = new web3.eth.Contract(
    temperatureMonitor.interface,
  );
return contract.deploy({
    data: temperatureMonitor.bytecode,
  })
  .send({
    from: address,
    gas: ‘0x2CD29C0’, 
    // Grant Permission to Contract by including nodes public keys
    privateFor: [publicKey],
  })
  .then((contract) => {
    return contract.options.address;
  });
}

Подобным же образом выполняются приватные транзакции — путем включения публичного ключа участников на момент выполнения.


async function setTemperature(web3, contractAddress, publicKey, temp) {
  const address = await getAddress(web3);
  const myContract = await getContract(web3, contractAddress);
return myContract.methods.set(temp).send({
    from: address,
    // Grant Permission by including nodes public  keys
    privateFor: [publicKey],
  }).then((receipt) => {
    return receipt.status;
  });
}

Теперь мы можем запустить private.js со следующими результатами:


node private.js
Contract address after deployment: 0x85dBF88B4dfa47e73608b33454E4e3BA2812B21D
[Node3] temp retrieved after updating contract from external nodes: null
[Node2] temp retrieved after updating contract from internal nodes: 12
[Node3] temp retrieved from external nodes after update null

Эксплорер Quorum в Chainstack покажет следующее:


  • деплой контракта от участника Супермаркет;
  • Выполнение SetTemperature от Третьей стороны;
  • Выполнение SetTemperature от участника Вендор.

image


Как видите, обе транзакции выполнены, но только транзакция от участника Вендор обновила температуру в контракте. Таким образом, приватные транзакции обеспечивают неизменяемость, но при этом и не выдают данные третьей стороне.


Заключение


Мы рассмотрели коммерческий сценарий использования Quorum для обеспечения актуальной информации о температуре в складском помещении путем развертывания сети между двумя сторонами — супермаркетом и владельцем склада.


Мы показали как актуальная информация о температуре может поддерживаться как через публичные, так и через приватные транзакции.


Сценариев применения может быть очень много и, как видите, это совсем не сложно.


Экспериментируйте, попробуйте развернуть свой сценарий. Тем более что более что индустрия блокчейн-технологий к 2024 году может вырасти почти в десять раз.

Tags:
Hubs:
+9
Comments1

Articles