Java sdk uses the loaded account private key to call the contract

Java sdk uses the loaded account private key to call the contract

1. Smart contract case

1.2 The process of smart contract

image-20230304233838681

1.2 Smart contract detailed code

Implemented a simple store function. It defines three structures: users, merchants and commodities, and the corresponding mapping relationships. It provides methods for registering users and merchants, creating items, updating user balances, purchasing items, and more. It also includes some modifiers to restrict only users or merchants from calling specific methods. The user's purchase of goods mainly involves detecting whether the current user has the user's authority, and the merchant produces and sells goods to detect whether the current user has the merchant's authority.

The main three structures

  • User
  • Merchant
  • Commodity

The three structures are users, merchants, and commodities.

The specific business relationship is as follows:

  1. User registration and merchant registration This contract allows users and merchants to register, userMapand merchantMapmaps the corresponding user addresses and information in and respectively.
  2. Create a product Merchants can call createCommoditythe method to create a new product, including product name, price and quantity, and store this product information in commodityMapand commoditysarrays , and map the product quantity to the corresponding merchant address (ie commodityToMerchantMap).
  3. Updating User Balance Users can update their account balance through updateBalancethe method .
  4. Buying Products Users can purchase existing products through buyCommoditythe method , which needs to pass in the product ID and purchase quantity. If the user balance is sufficient and the product inventory is sufficient, the purchase will be successful, and the merchant will receive the corresponding payment.
  5. Querying Products by Pagination Users can view all products through queryAllCommoditysthe method and display them in a page-by-page manner.
  6. Query user information and business information Both users and businesses queryUserInfocan queryMerchantInfoquery their own information through and methods.
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;


contract Shop {
    // 用户的结构体
    struct User {
        address userAddress;
        string  userName;
        uint256 userBalance;
        Role    role;
    }
    
    // 商家的结构体
    struct Merchant {
        address merchantAddress;
        uint256 merchantBalance;
        Role    role;
    }
    
    // 商品的结构体
    struct Commodity {
        uint256 commodityId;
        string  commodityName;
        uint256 commodityPrice;
        uint256 commodityQuantity;
    }
    
    // 资产ID
    uint256 public commodityCount;
    // 角色枚举
    enum Role { UserType,MerchantType }
    
    // 地址映射用户信息
    mapping(address => User) userMap;
    // 地址映射商家信息
    mapping(address => Merchant) merchantMap;
    // 商品ID映射商品信息
    mapping(uint256 => Commodity) commodityMap;
    // 商品ID映射售卖的商家
    mapping(uint256 => address) commodityToMerchantMap;
    
    // 存储所有商品
    Commodity[] commoditys;
    
    
    // 判断当前是不是用户
    modifier AuthUser(address _userAddress) {
        require(userMap[_userAddress].role == Role.UserType,"当前不是用户");
        _;
    }
    
    // 判断当前是不是商家
    modifier AuthMerchant(address _merchantAddress) {
        require(merchantMap[_merchantAddress].role == Role.MerchantType,"当前不是商家");
        _;
    }
    // 注册事件 
    event Registered(address indexed _Address);
	// 商家添加商品事件
	event CreateCommodity(address indexed _Address,string _name);
	// 用户更新余额事件
    event UpdateBalance(address indexed _Address,uint256 indexed _amount);
    // 用户购买商品事件
    event BuyCommodity(address indexed _Address,uint256 indexed _commodityId);
    
    // 企业注册
    function registerUser(
   		address _userAddress,
   		string _userName
   	) public {
        User memory user = User({
            userAddress: _userAddress,
            userName: _userName,
            userBalance: 0,
            role: Role.UserType
        });
        userMap[_userAddress] = user;
        emit Registered(_userAddress);
    }
    
    // 商家注册
    function registerMerchant(
   		address _merchantAddress,
   		uint256 _merchantBalance
   	) public {
        Merchant memory merchant = Merchant({
            merchantAddress: _merchantAddress,
            merchantBalance: _merchantBalance,
            role: Role.MerchantType
        }); 
        merchantMap[_merchantAddress] = merchant;
        emit Registered(_merchantAddress);
    }
    
    
    // 创建资产 
    function createCommodity(
    	string memory _name,
    	uint256 _price,
    	uint256 _quantity
    ) public AuthMerchant(msg.sender) {
        Commodity memory commodity = Commodity({
            commodityId: commodityCount,
            commodityName: _name,
            commodityPrice: _price,
            commodityQuantity: _quantity
        });
        commodityMap[commodityCount] = commodity;
        commoditys.push(commodity);
        commodityToMerchantMap[commodityCount] = msg.sender;
        commodityCount++;
        emit CreateCommodity(msg.sender,_name);
    }
    
    // 更新余额
    function updateBalance(
    	address _userAddress,
    	uint256 _amount
    ) public AuthUser(msg.sender) {
        userMap[_userAddress].userBalance += _amount;
        emit UpdateBalance(msg.sender,_amount);
    }
    
    // 用户购买一个物品
    function buyCommodity(
    	uint256 _commodityId,
    	uint256 _amount
    ) public AuthUser(msg.sender) {
        require(userMap[msg.sender].userBalance >= _amount,"当前余额不足");
        require(commodityMap[_commodityId].commodityQuantity != 0,"当前的物品没有库存");
        userMap[msg.sender].userBalance -= _amount;
        commodityMap[_commodityId].commodityQuantity--;
        merchantMap[commodityToMerchantMap[_commodityId]].merchantBalance += _amount;
        for (uint i = 0; i < commoditys.length; i++){
            if (commoditys[i].commodityId == _commodityId){
                commoditys[i].commodityQuantity--;
            }
        }
        emit BuyCommodity(msg.sender,_commodityId);
    }
    
    
    // 分页查询商品
    function queryAllCommoditys(
    	uint256 page,
    	uint256 pageSize
    ) public returns(Commodity[] memory) {
        require(page > 0, "页数不能为0");
        uint256 startIndex = (page - 1) * pageSize; // 计算起始索引
        uint256 endIndex = startIndex + pageSize > commoditys.length ? commoditys.length : startIndex + pageSize; // 计算结束索引
        Commodity[] memory CommodityArr = new Commodity[](endIndex - startIndex); // 创建每页大小的 Enterprise 数组
        for (uint i = startIndex; i < endIndex; i++){
            CommodityArr[i - startIndex] = commoditys[i];
        }
        return CommodityArr;
    }
    
    // 查询用户信息
    function queryUserInfo() public returns(
    	address,
    	string memory,
    	uint256,
    	Role
    ) {
        User memory user = userMap[msg.sender];
        return (user.userAddress,user.userName,user.userBalance,user.role);
    }
    
    // 查询商家信息
    function queryMerchantInfo() public AuthMerchant(msg.sender) returns(
    	address,
    	uint256,
    	Role
    )  {
        Merchant memory merchant = merchantMap[msg.sender];
        return (merchant.merchantAddress,merchant.merchantBalance,merchant.role);
    }
    
}

1.3 Export using WeBASE-Front

Use the FISCO BCOS blockchain platform and WeBASE-Front to deploy smart contracts and export Java projects.

image-20230304234059356

Use IDEA to view the current project.

image-20230304234346997

2. Java SDK loads the private key to call the contract

scenes to be used

When Webase-front deploys smart contracts, the switching of private keys is usually used in the following scenarios:

  1. Deploying smart contracts : When deploying smart contracts, you need to use the private key of the deployment account to sign the contract to ensure that the contract is correctly deployed to the blockchain network.
  2. Calling a smart contract : When calling a smart contract, the private key of the calling account needs to be used to sign the call to ensure the security and credibility of the calling request.
  3. Manage smart contracts : When managing smart contracts, it may be necessary to use different account private keys for operations, such as updating contracts, authorizing other accounts to operate contracts, and so on.

In these scenarios, the switching of the private key can ensure the security and credibility of the smart contract, and can also protect the private information of the account from being leaked.

How the private key is stored

  • The Java SDK supports loading through private key strings or files, so the account's private key can be stored in 数据库or 本地文件.
  • Local files support two storage formats, one in PKCS12encrypted format and PEMone in plaintext format.
  • When developing business, you can choose the storage and management method of the private key according to the actual business scenario.

Here I choose to encrypt and store the private key in the database, and query the private key or public key of the address through the user's address. When the user needs permission, obtain the private key through the user's address, and pass the into the method of calling the contract. Break away from the default contract caller.

There are ABI-based and BIN-based methods for calling contracts in the Java SDK.

  • Official website link - based on ABI and BIN: https://fisco-bcos-documentation.readthedocs.io/zh_CN/v2.8.0/docs/sdk/java_sdk/assemble_transaction.html
  • Official website link - account management: https://fisco-bcos-documentation.readthedocs.io/zh_CN/v2.8.0/docs/sdk/java_sdk/key_tool.html

The following is the complete project structure diagram.

image-20230305153951516

2.1 Create a database table to store public and private keys

Although the private key and public key are stored in Mysql, it is still not very safe, so salt encryption is required when storing.

create database shop default character set utf8mb4;

create table shop_userKey(
    user_address varchar(60) comment '用户地址',
    user_privateKey text comment '用户私钥',
    user_publicKey text comment '用户公钥'
) comment '用户的公私钥表';

2.2 Gradlew adds dependencies

Add the corresponding dependencies in dependencies.

  • mybatis-plus
  • mysql-connector-java
  • fastjson
	compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.0'
    implementation 'mysql:mysql-connector-java:8.0.27'
    implementation group: 'com.alibaba', name: 'fastjson', version: '2.0.24'
    implementation group: 'org.apache.directory.studio', name: 'org.apache.commons.codec', version: '1.8'

2.3 Connect to the database

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&useSSL=false

mybatis.configuration.cache-enabled=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
mybatis-plus.global-config.id-type=auto
mybatis-plus.global-config.db-config.table-prefix=shop_

2.3 Create an entity class object

Creating UserKeyobjects is mainly used to operate the database. When the user registers, the address and the public and private keys need to be returned to the user, and the private key is only provided for one download.

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName(value = "shop_userKey")
public class UserKey {
    
    

    @TableField(value = "user_address")
    private String userAddress;
    @TableField(value = "user_privateKey")
    private String userPrivateKey;
    @TableField(value = "user_publicKey")
    private String userPublicKey;
}

Create a User object to return the data of the user on the front-end page.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    
    
    // 用户地址
    private String  userAddress;
    // 用户名称
    private String  userName;
    // 用户余额
    private int     userBalance;
    // 用户角色
    private int     role;
}

Create a Merchant object to return the merchant information on the front-end page.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigInteger;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Merchant {
    
    
    
    // 商家地址
    private String  merchantAddress;
	// 商家余额
    private int 	merchantBalance;
	// 用户角色
    private int 	role;
}

Create a Commodity object to return the product information or product collection on the front-end page

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigInteger;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Commodity {
    
    
    // 商品ID
    private  int commodityId;
    // 商品名称
    private  String  commodityName;
    // 商品价格
    private  int commodityPrice;
    // 商品数量
    private  int  commodityQuantity;
}

2.4 Data Persistence Layer

Create the interface of UserKeyMapper, inherit the interface of Mybatis-plus, and realize the CRUD operation on the database.

Here are mainly query and delete operations, which are temporarily needed for follow-up tests. If there are specific scenarios, detailed operations are required.

@Mapper
public interface UserKeyMapper extends BaseMapper<UserKey> {
    
    
}


public interface UserKeyService extends IService<UserKey> {
    
    
    public UserKey selectByUserAddress(String address);

    public boolean deleteByUserAddress(String address);
}


@Service
public class UserKeyServiceImpl extends ServiceImpl<UserKeyMapper, UserKey> implements UserKeyService {
    
    

    @Autowired
    private UserKeyMapper userKeyMapper;
    @Override
    public UserKey selectByUserAddress(String address) {
    
    
        if (address.isEmpty()){
    
    
            return null;
        }
        LambdaQueryWrapper<UserKey> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserKey::getUserAddress, address);
        return userKeyMapper.selectOne(queryWrapper);
    }

    @Override
    public boolean deleteByUserAddress(String address) {
    
    
        if (address.isEmpty()){
    
    
            return false;
        }
        LambdaQueryWrapper<UserKey> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserKey::getUserAddress, address);
        userKeyMapper.delete(queryWrapper);
        return true;
    }
}

2.5 Use AES algorithm encryption tool class

The AES algorithm is a symmetric key encryption algorithm used to protect the confidentiality of data. It uses the same key for encryption and decryption operations. In the AES algorithm, data is divided into fixed-length blocks and encrypted through multiple rounds of key mixing and substitution operations. Decryption converts ciphertext into plaintext through the reverse operation. The AES algorithm is widely recognized for its high security, high efficiency and wide application.

package org.example.Shop.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

public class EncryptionUtils {
    
    

    private static final String ALGORITHM = "AES"; // 加密算法
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; // 加密模式

    private static final String KEY = "MySecretKey12345";
    /**
     * 加密数据
     *
     * @param data 待加密的数据
     * @param KEY 密钥
     * @return 加密后的数据
     */
    public static String encrypt(String data) throws Exception {
    
    
        Key secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密数据
     *
     * @param encryptedData 加密后的数据
     * @param KEY 密钥
     * @return 解密后的数据
     */
    public static String decrypt(String encryptedData) throws Exception {
    
    
        Key secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

2.6 Business layer implementation

After exporting the Java project, there are written business methods in the service layer, which can be called directly, but some methods and services do not meet the requirements, such as switching accounts to call the contract, and loading the user's private key to call the contract for transactions .

  • Business default for user registration
  • Business default for merchant registration

The Java SDK org.fisco.bcos.sdk.crypto.CryptoSuiteprovides the account generation function.

// 创建非国密类型的CryptoSuite
CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
// 随机生成非国密公私钥对
CryptoKeyPair cryptoKeyPair = cryptoSuite.createKeyPair();
// 获取账户地址
String accountAddress = cryptoKeyPair.getAddress();
// 获取账户私钥
String hexPrivateKey = cryptoKeyPair.getHexPrivateKey();
// 获取账户公钥
String hexPublicKey = cryptoKeyPair.getHexPublicKey();

Load the user's private key:

CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
// 加载用户私钥        
CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));

Since the address here is randomly generated, there is no pemway p12to save and get it.

As shown below:

The merchant needs to create an apple, but the user and address that create the apple must belong to the merchant, and cannot be created by other unregistered or purchased users.

image-20230305162713999

2.6.1 Add user registration to obtain public and private keys

In UserKeyServiceImplthe implementation class of , add a method makeNewUserKey()to generate the randomly generated address and public-private key when the user newly registers, and return the encrypted public-private key to the database.

@Service
public class UserKeyServiceImpl extends ServiceImpl<UserKeyMapper, UserKey> implements UserKeyService {
    
    

    @Autowired
    private UserKeyMapper userKeyMapper;

    @SneakyThrows
    @Override
    public UserKey makeNewUserKey() {
    
    
        // 创建非国密类型的CryptoSuite
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        // 随机生成非国密公私钥对
        CryptoKeyPair cryptoKeyPair = cryptoSuite.createKeyPair();
        // 获取账户地址
        String keyPairAddress = cryptoKeyPair.getAddress();
        // 获取账户私钥
        String hexPrivateKey = cryptoKeyPair.getHexPrivateKey();
        // 获取账户公钥
        String hexPublicKey = cryptoKeyPair.getHexPublicKey();

        UserKey userKey = new UserKey();
        userKey.setUserAddress(keyPairAddress);
        // 对数据进行AES算法加密
        userKey.setUserPrivateKey(EncryptionUtils.encrypt(hexPrivateKey));
        userKey.setUserPublicKey(EncryptionUtils.encrypt(hexPublicKey));
        // 加密后存储数据库
        userKeyMapper.insert(userKey);
        return userKey;
    }
    // 其他业务代码
	......
}

2.6.2 Add and load account private key

The following operations are modified in the servicedefault exported ShopServiceclass of the layer.

image-20230305163216365

This is a public method that can be reused, and the user and the merchant pass the private key to the call of the contract.

  /**
   * 加载用户账户切换业务
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  private TransactionResponse getTransactionResponse(CryptoKeyPair cryptoKeyPair,String funcName,List<Object> params) throws Exception {
    
    
      // 过创建和使用AssembleTransactionProcessor对象来调用和查询等操作。不部署合约,那么就不需要复制binary文件
    AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory
            .createAssembleTransactionProcessor(client, cryptoKeyPair,"src/main/resources/abi/","");
      // 使用同步方式发送交易
    TransactionResponse transactionResponse = transactionProcessor
            .sendTransactionAndGetResponseByContractLoader("Shop",this.address,funcName, params);
    return transactionResponse;
  }

2.6.3 Add merchant to load private key

After the merchant registers, the address information needs to be saved. When calling the contract, the merchant’s address is carried, and the encrypted private key in the database is obtained through the merchant’s address query. Then, this method is mainly for the merchant to pass in the private key and the contract method. By loading the merchant’s private key key, you can call the method of creating a product.

  /**
   * 商家添加商品信息
   * @param input
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public boolean createCommodity(CryptoKeyPair cryptoKeyPair,ShopCreateCommodityInputBO input) throws Exception {
    
    
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"createCommodity", input.toArgs());
    return transactionResponse.getReceiptMessages().equals("Success");
  }

  /**
   * 查询商家的信息
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public Merchant queryMerchantInfo(CryptoKeyPair cryptoKeyPair, List<Object> params) throws Exception {
    
    
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"queryMerchantInfo", params);
    return Optional.of(transactionResponse)
            .filter(response -> "Success".equals(response.getReceiptMessages()))
            .map(response -> JSON.parseArray(response.getValues()))
            .filter(objects -> objects.size() >= 3)
            .map(objects -> {
    
    
              Merchant merchant = new Merchant();
              merchant.setMerchantAddress(objects.getString(0));
              merchant.setMerchantBalance(objects.getInteger(1));
              merchant.setRole(objects.getInteger(2));
              return merchant;
            })
            .orElse(null);
  }

2.6.4 Add user to load private key

The operations that require the user to load the private key are:

  • Update user's balance
  • User purchases an item

The operation here is the same as above, and it is necessary to verify whether the user’s address of the current private key is the user’s address after the user loads the private key.

  /**
   * 用户更新账户余额
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse updateBalance(CryptoKeyPair cryptoKeyPair,ShopUpdateBalanceInputBO input) throws Exception {
    
    
    return getTransactionResponse(cryptoKeyPair,"updateBalance", input.toArgs());
  }

  /**
   * 用户购买商品
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse buyCommodity(CryptoKeyPair cryptoKeyPair,ShopBuyCommodityInputBO input) throws Exception {
    
    
    return getTransactionResponse(cryptoKeyPair,"buyCommodity", input.toArgs());
  }

2.6.5 Complete business code

@Service
@NoArgsConstructor
@Data
public class ShopService {
    
    
  public static final String ABI = org.example.Shop.utils.IOUtil.readResourceAsString("abi/Shop.abi");

  public static final String BINARY = org.example.Shop.utils.IOUtil.readResourceAsString("bin/ecc/Shop.bin");

  public static final String SM_BINARY = org.example.Shop.utils.IOUtil.readResourceAsString("bin/sm/Shop.bin");

  @Value("${system.contract.shopAddress}")
  private String address;

  @Autowired
  private Client client;

  AssembleTransactionProcessor txProcessor;

  @PostConstruct
  public void init() throws Exception {
    
    
    this.txProcessor = TransactionProcessorFactory.createAssembleTransactionProcessor(this.client, this.client.getCryptoSuite().getCryptoKeyPair());
  }

  /**
   * 用户更新账户余额
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse updateBalance(CryptoKeyPair cryptoKeyPair,ShopUpdateBalanceInputBO input) throws Exception {
    
    
    return getTransactionResponse(cryptoKeyPair,"updateBalance", input.toArgs());
  }

  /**
   * 用户购买商品
   * @param cryptoKeyPair
   * @param input
   * @return
   * @throws Exception
   */
  public TransactionResponse buyCommodity(CryptoKeyPair cryptoKeyPair,ShopBuyCommodityInputBO input) throws Exception {
    
    
    return getTransactionResponse(cryptoKeyPair,"buyCommodity", input.toArgs());
  }

  public TransactionResponse registerUser(ShopRegisterUserInputBO input) throws Exception {
    
    
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "registerUser", input.toArgs());
  }

  /**
   * 查询商家的信息
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public Merchant queryMerchantInfo(CryptoKeyPair cryptoKeyPair, List<Object> params) throws Exception {
    
    
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"queryMerchantInfo", params);
    return Optional.of(transactionResponse)
            .filter(response -> "Success".equals(response.getReceiptMessages()))
            .map(response -> JSON.parseArray(response.getValues()))
            .filter(objects -> objects.size() >= 3)
            .map(objects -> {
    
    
              Merchant merchant = new Merchant();
              merchant.setMerchantAddress(objects.getString(0));
              merchant.setMerchantBalance(objects.getInteger(1));
              merchant.setRole(objects.getInteger(2));
              return merchant;
            })
            .orElse(null);
  }

  /**
   * 加载用户账户切换业务
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  private TransactionResponse getTransactionResponse(CryptoKeyPair cryptoKeyPair,String funcName,List<Object> params) throws Exception {
    
    
    AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory
            .createAssembleTransactionProcessor(client, cryptoKeyPair,"src/main/resources/abi/","");

    TransactionResponse transactionResponse = transactionProcessor
            .sendTransactionAndGetResponseByContractLoader("Shop",this.address,funcName, params);
    return transactionResponse;
  }

  /**
   * 商家添加商品信息
   * @param input
   * @param cryptoKeyPair
   * @param params
   * @return
   * @throws Exception
   */
  public boolean createCommodity(CryptoKeyPair cryptoKeyPair,ShopCreateCommodityInputBO input) throws Exception {
    
    
    TransactionResponse transactionResponse = getTransactionResponse(cryptoKeyPair,"createCommodity", input.toArgs());
    return transactionResponse.getReceiptMessages().equals("Success");
  }

  public TransactionResponse queryAllCommoditys(ShopQueryAllCommoditysInputBO input) throws Exception {
    
    
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "queryAllCommoditys", input.toArgs());
  }

  public TransactionResponse queryUserInfo() throws Exception {
    
    
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "queryUserInfo", Arrays.asList());
  }

  public TransactionResponse registerMerchant(ShopRegisterMerchantInputBO input) throws Exception {
    
    
    return this.txProcessor.sendTransactionAndGetResponse(this.address, ABI, "registerMerchant", input.toArgs());
  }

  public CallResponse commodityCount() throws Exception {
    
    
    return this.txProcessor.sendCall(this.client.getCryptoSuite().getCryptoKeyPair().getAddress(), this.address, ABI, "commodityCount", Arrays.asList());
  }
}

2.7 Control layer implementation

This is mainly for testing. For more security risks, POST requests need to be used in the actual development mode and cannot be exposed.

2.7.1 RegisterController

The main realization is:

  1. user registration interface
  2. Merchant registration interface
  3. Users and merchants create new addresses and random public-private key interfaces
API interface request type request parameters
/register GET none
/register/user POST ShopRegisterUserInputBO
/register/merchant POST ShopRegisterMerchantInputBO

Example of user request interface:

http://localhost:8088/register/user

{
    
    
    "_userAddress": "0xaf9f11d639600dffa32ce7f1c3474d1e280cf791",
    "_userName": "张三"
}

An example of a merchant's request interface:

http://localhost:8088/register/merchant

{
    
    
    "_merchantAddress": "0xaf9f11d639600dffa32ce7f1c3474d1e280cf791",
    "_merchantBalance": 1000
}
@RestController
@RequestMapping("register")
public class RegisterController {
    
    

    @Autowired
    private UserKeyService userKeyService;

    @Autowired
    private ShopService shopService;

    /**
     * 用户注册创建新的随机地址和公私钥
     * @return
     */
    @SneakyThrows
    @GetMapping
    public CommonResponse getAddressAndPrivateKey(){
    
    
        return CommonResponse.ok("Success",userKeyService.makeNewUserKey());
    }

    /**
     * 用户注册
     * @param registerUserInputBO
     * @return
     */
    @SneakyThrows
    @PostMapping("user")
    public CommonResponse registerUser(@RequestBody ShopRegisterUserInputBO registerUserInputBO){
    
    
        if (StringUtils.isBlank(registerUserInputBO.get_userAddress()) || StringUtils.isBlank(registerUserInputBO.get_userName())){
    
    
            return CommonResponse.fail("400",new RuntimeException("参数为空"));
        }
        TransactionResponse transactionResponse = shopService.registerUser(registerUserInputBO);
        return CommonResponse.ok("注册成功",transactionResponse.getReceiptMessages());
    }

    /**
     * 商家注册
     * @param registerMerchantInputBO
     * @return
     */
    @SneakyThrows
    @PostMapping("merchant")
    public CommonResponse registerMerchant(@RequestBody ShopRegisterMerchantInputBO registerMerchantInputBO){
    
    
        if (StringUtils.isBlank(registerMerchantInputBO.get_merchantAddress())){
    
    
            return CommonResponse.fail("400",new RuntimeException("参数为空"));
        }
        TransactionResponse transactionResponse = shopService.registerMerchant(registerMerchantInputBO);
        return CommonResponse.ok("注册成功",transactionResponse.getReceiptMessages());
    }
}

2.7.2 UserController

The main realization is:

  1. The interface for the user to obtain the list of products
  2. Interface for users to purchase goods
  3. Interface for user to update account balance
API interface request type request parameters
/users/{address} POST ShopQueryAllCommoditysInputBO
/users/{address} POST ShopBuyCommodityInputBO
/users/{address} PUT BigInteger

User interface example:

// 查询商品的列表
POST http://localhost:8088/users/{
    
    address}

{
    
    
    "page": 1,
    "pageSize": 4
}

// 用户购买商品
POST http://localhost:8088/users/{
    
    address}
{
    
    
    "_commodityId": 1,
    "_amount": 100
}

// 更新用户的余额
PUT http://localhost:8088/users/{
    
    address}?balance=1000
@RestController
@RequestMapping("users")
public class UserController {
    
    

    @Autowired
    private Client client;
    @Autowired
    private ShopService shopService;
    @Autowired
    private UserKeyService userKeyService;

    @SneakyThrows
    @GetMapping("{address}")
    public CommonResponse getCommodityList(@PathVariable String address,@RequestBody ShopQueryAllCommoditysInputBO commoditysInputBO){
    
    
        if (address.isEmpty() || commoditysInputBO.toArgs().isEmpty()){
    
    
            return CommonResponse.fail("400",new RuntimeException("查询异常"));
        }
        TransactionResponse transactionResponse = shopService.queryAllCommoditys(commoditysInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
    
    
            String responseValues = transactionResponse.getValues();
            int pageSize = commoditysInputBO.getPageSize();
            // 使用 intStream.range() 方法生成 [0, pageSize-1] 的整数流
            List<Commodity> commodities = IntStream.range(0, pageSize)
                    // 把每一个整数映射成一个 JSONArray 对象
                    .mapToObj(i -> JSON.parseArray(responseValues).getJSONArray(0).getJSONArray(i))
                    // 把每个 JSONArray 转换成 Commodity 对象
                    .map(jsonArray -> {
    
    
                        // 根据 jsonArray 创建一个 Commodity 对象
                        Commodity commodity = new Commodity();
                        commodity.setCommodityId((Integer) jsonArray.get(0));
                        commodity.setCommodityName((String) jsonArray.get(1));
                        commodity.setCommodityPrice((Integer) jsonArray.get(2));
                        commodity.setCommodityQuantity((Integer) jsonArray.get(3));
                        return commodity;
                    })
                    // 把所有 Commodity 对象收集到一个 List 中
                    .collect(Collectors.toList());
            return CommonResponse.ok("查询成功",commodities);
        }
        return CommonResponse.fail("400",new RuntimeException("查询异常"));
    }

    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse buyCommodity(@PathVariable String address, @RequestBody ShopBuyCommodityInputBO commodityInputBO){
    
    
        if (StringUtils.isBlank(address)){
    
    
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 购买商品操作
        TransactionResponse transactionResponse = shopService.buyCommodity(cryptoKeyPair, commodityInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
    
    
            return CommonResponse.ok("购买成功",address);
        }
        return CommonResponse.fail("400",new RuntimeException("购买失败"));
    }

    @SneakyThrows
    @PutMapping("{address}")
    public CommonResponse updateBalance(@PathVariable String address,@RequestParam BigInteger balance){
    
    
        if (StringUtils.isBlank(address)){
    
    
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite
                .getKeyPairFactory()
                .createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));

        ShopUpdateBalanceInputBO balanceInputBO = new ShopUpdateBalanceInputBO(address, balance);
        // 更新账户余额操作
        TransactionResponse transactionResponse = shopService.updateBalance(cryptoKeyPair,balanceInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
    
    
            return CommonResponse.ok("更新成功",balance);
        }
        return CommonResponse.fail("400",new RuntimeException("更新失败"));
    }
}

2.7.3 MerchantController

The main realization is:

  1. The interface to view the internal information of the merchant
  2. Interface for merchants to create products
API interface request type request parameters
/merchants?address={address} GET address
/merchants/address POST ShopCreateCommodityInputBO

An example of a merchant's interface request:

GET http://localhost:8088/merchants/?address={
    
    address}

POST http://localhost:8088/merchants/address
{
    
    
    "_name": "苹果",
    "_price": 100,
    "_quantity": 20
}
@RestController
@RequestMapping("merchants")
public class MerchantController {
    
    
    @Autowired
    private UserKeyService userKeyService;

    @Autowired
    private ShopService shopService;

    /**
     * 查询商家信息接口
     *
     * @param address   用户地址(商家地址)
     * @return          响应结果对象
     */
    @SneakyThrows
    @GetMapping
    public CommonResponse queryMerchant(@RequestParam String address)  {
    
    
        if (StringUtils.isEmpty(address)){
    
    
            return CommonResponse.fail("400",new RuntimeException("查询失败"));
        }
        // 获取商家用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = null;
        cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        List<Object> params = new ArrayList<>();

        // 调用查询商家信息 假设这是商家的内部信息 普通用户不可查询
        Merchant merchant = shopService.queryMerchantInfo(cryptoKeyPair, params);
        if (Objects.isNull(merchant)){
    
    
            return CommonResponse.fail("400",new RuntimeException("当前角色不是商家"));
        }
        return CommonResponse.ok("查询成功",merchant);
    }

    /**
     * 创建商品接口
     *
     * @param address                   用户地址(商家地址)
     * @param createCommodityInputBO     商品信息参数对象
     * @return                          响应结果对象
     */
    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse createCommodity(@PathVariable String address,@RequestBody ShopCreateCommodityInputBO createCommodityInputBO){
    
    
        if (StringUtils.isEmpty(createCommodityInputBO.get_name())){
    
    
            return CommonResponse.fail("400",new RuntimeException("商品的名称不能为空"));
        }
        // 获取商家用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        System.out.println(userKey.getUserPrivateKey());
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 商家调用创建商品
        boolean commodity = shopService.createCommodity(cryptoKeyPair, createCommodityInputBO);
        return CommonResponse.ok("创建成功",commodity);
    }
}

2.8 API testing

2.8.1 Test registered users

GET http://localhost:8088/register

POST http://localhost:8088/register/user

1. Send a GET request to obtain the following information:

  • User's address:0x17b1730bb77db57313ac59c20c0aaae2efbaeab6
  • User encrypted private key:dzuF553WpMInWPeLqjP/dyZ4s381sEPeQni4+mY/L7Y50P46JZkLXiV7RaMYXjMxz1AD+cq9ktjWH4ZCX2jOXAbb0lJO2pSgRxctIDarhYM=

image-20230305221854226

2. Use the above user address to register a user named "Zhang San". The picture below shows that the registration is successful.

image-20230305222248111

3. Here it shows that the registration is successful, and then we decrypt it through the encryption tool class of the AES algorithm as follows:

Get the decrypted private key:c8c25e5a9bc3485e90a2cab239861eae9e51a2d57bbb60ea7eda9a4c83267eac

image-20230305222758718

4. Import the private key in WeBASE-Front, and create zhangsana test user.

image-20230305222932099

After successful import, directly use queryUserInfothe contract called by this account. Because when I wrote to view the user contract, I added a check to see if the current address has permission.

image-20230305223114434

It can be found that the following is the direct transaction is successful, and I can view 张三the information.

  • User address of zhangsan:0x17B1730bb77db57313aC59C20C0AAAe2EFBAeAB6

Compare the user address obtained by the above interface test, the user address is exactly the same.

image-20230305223623743

2.8.2 Test registered merchants

GET http://localhost:8088/register

POST http://localhost:8088/register/merchant

1. Send a GET request to obtain the following information:

  • Business address:0x4ab865d51fbf01e2a102a86e56fe9e7358382dce
  • Merchant encrypted private key:SxWg6lFxmvmp/uyIA0GBmkE1iaCh6jioFigyk+VSbTP4OuyK4HGSbinGKFTzDmwft7dz5hOsiJyUgQ0lQXeuqwbb0lJO2pSgRxctIDarhYM=

image-20230305224313691

2. Use the new user address to register a business, and the registered capital is 1,000 yuan.

image-20230305224500695

3. Obtain the merchant's private key after decryption

  • Merchant's private key:f143333a17243b5d804c4162adbb68bf28fc75dfb7e3db680d48a1f1e2dfcb48

Import the name of the private key merchant in WeBASE-Front merchant, and call the method of querying the merchant.

image-20230305224817909

Call the current queryMerchantInfofunction to view the transaction as follows:

  • Merchant user address:0x4aB865D51fbf01E2A102A86e56Fe9E7358382DcE

image-20230305224954366

When the user I use zhangsangoes to view the internal information of the company, the transaction will fail. Show is not currently a business.

image-20230305225216954

4. Use the interface test to query the merchant's information, and the result is the same as above.

image-20230305225905882

2.8.3 Test the merchant's added products

POST http://localhost:8088/merchants/0x4ab865d51fbf01e2a102a86e56fe9e7358382dce

1. Using the address of the merchant 0x4ab865d51fbf01e2a102a86e56fe9e7358382dce, add two items, one is apple and the other is banana. Added successfully as follows.

image-20230305230204202

image-20230305230228403

2. Use user pagination to query all current data.

image-20230305235634870

2.8.4 Test user purchases goods

PUT http://localhost:8088/users/{address}?balance=1000

POST http://localhost:8088/users/{address}

1. Update the balance of the user's account by 100 yuan. Then query the balance of the current user.

image-20230306012030231

The following is the balance viewed using WeBASE-Front.

image-20230306013545696

2. When users purchase goods, it is determined that only registered users can purchase successfully.

image-20230306012341889

Call zhangsan's account through WeBASE-Front to check personal information, successfully deduct money and purchase goods.

image-20230306012519581

View all the current product information, the number of bananas has been deducted is 1.

image-20230306012718338

Check the current merchant balance, 100 yuan has been received.

image-20230306013035272

2.8.5 Test whether the private key is loaded

1. I deliberately wrote a trigger event in the contract where the user purchases the product. Assuming that the user purchases the product, it will record who purchased the current address. You can check whether the address of the Event event belongs to the user msg.sender.

Here are the detailed parts of the contract:

  • There is an AuthUser modifier function to determine whether the current user has the authority
  • Event with BuyCommodity

image-20230306014021787

2. In the part where the user purchases the product, that is, the interface that the user accesses when purchasing the product, add the user address for obtaining the current contract call. Use log to view.

The following is the added test code for observation.

    @SneakyThrows
    @PostMapping("{address}")
    public CommonResponse buyCommodity(@PathVariable String address, @RequestBody ShopBuyCommodityInputBO commodityInputBO){
    
    
        if (StringUtils.isBlank(address)){
    
    
            return CommonResponse.fail("400", new RuntimeException("当前用户无效"));
        }
        // 获取当前用户的私钥,并使用其创建加密套件及密钥对
        UserKey userKey = userKeyService.selectByUserAddress(address);
        CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        CryptoKeyPair cryptoKeyPair = cryptoSuite.getKeyPairFactory().createKeyPair(EncryptionUtils.decrypt(userKey.getUserPrivateKey()));
        // 购买商品操作
        TransactionResponse transactionResponse = shopService.buyCommodity(cryptoKeyPair, commodityInputBO);
        if (transactionResponse.getReceiptMessages().equals("Success")){
    
    
            log.info("======================================================");
            log.info("++++++++++++当前的合约调用者:" + transactionResponse.getTransactionReceipt().getFrom());
            log.info("++++++++++++当前的合约调用者:" + transactionResponse.getTransactionReceipt().getLogs().get(0).getTopics().get(1));
            return CommonResponse.ok("购买成功",address);
        }
        return CommonResponse.fail("400",new RuntimeException("购买失败"));
    }

Call this interface to continue testing.

image-20230306023304521

3. View the current console output.

image-20230306023714077

4. Use the Event view of WeBASE-Front.

From the logs and Event events, the caller of the contract is 0x17b1730bb77db57313ac59c20c0aaae2efbaeab6the address.

image-20230306023811570

Based on the above cases, we can understand the usage scenarios of private key loading.

Guess you like

Origin blog.csdn.net/weixin_46532941/article/details/129909034