本地构建交易

本文将通过示例来向开发者介绍本地构造交易。本地构造交易用于构建交易内部数据,如果开发者有离线签名,交易备注等需求,可以参考此文内容。这些数据主要包括:

message raw {
    bytes ref_block_bytes = 1;//最新块高度的第6到8(不包含)之间的字节
    int64 ref_block_num = 3;//区块高度,可选
    bytes ref_block_hash = 4;//最新块的hash的第8到16(不包含)之间的字节
    int64 expiration = 8;//交易过期时间。最新块的时间加N分钟。N的大小根据需要设定(在本地节点的配置文件中修改,公共节点默认过期时间1分钟),后台的判断条件是(Expiration > 最新块时间 and Expiration < 最新块时时 + 24小时),如果条件成立则交易合法,否则交易为过期交易,不会被mainnet接收。
    repeated authority auths = 9;//权限信息
    // data not used
    bytes data = 10;//未使用字段,可以添加备注信息,可选
    //only support size = 1,  repeated list here for extension
    repeated Contract contract = 11;//合约信息
    // scripts not used
    bytes scripts = 12;//未使用字段,可以添加备注信息,可选
    int64 timestamp = 14;//交易创建时间
    int64 fee_limit = 18;//因资源不足,消耗trx的阀值
  }

📘

注:

目前只支持RPC接口

Java示例

这里可以通过wallet-cli来引入RPC接口的调用环境。

package org.tron.demo;

import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.tron.api.GrpcAPI.Return;
import org.tron.api.GrpcAPI.TransactionExtention;
import org.tron.common.crypto.ECKey;
import org.tron.common.crypto.Sha256Sm3Hash;
import org.tron.common.utils.ByteArray;
import org.tron.core.exception.CancelException;
import org.tron.protos.Contract;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.Transaction;
import org.tron.walletserver.WalletApi;

import java.util.Arrays;

public class TransactionSignDemo {
/*设置参照块数据*/
  public static Transaction setReference(Transaction transaction, Block newestBlock) {
    long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber();
    byte[] blockHash = getBlockHash(newestBlock).getBytes();
    byte[] refBlockNum = ByteArray.fromLong(blockHeight);
    Transaction.raw rawData = transaction.getRawData().toBuilder()
            .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16)))
            .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8)))
            .setRefBlockNum(blockHeight)
            .build();
    return transaction.toBuilder().setRawData(rawData).build();
  }

  public static Sha256Sm3Hash getBlockHash(Block block) {
    return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray());
  }

  public static String getTransactionHash(Transaction transaction) {
    String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()));
    return txid;
  }

  public static Transaction createTransaction(byte[] from, byte[] to, long amount) {
    Transaction.Builder transactionBuilder = Transaction.newBuilder();
    Block newestBlock = WalletApi.getBlock(-1);
/*设置合约内部数据*/
    Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder();
    Contract.TransferContract.Builder transferContractBuilder =
        Contract.TransferContract.newBuilder();
    transferContractBuilder.setAmount(amount);
    ByteString bsTo = ByteString.copyFrom(to);
    ByteString bsOwner = ByteString.copyFrom(from);
    transferContractBuilder.setToAddress(bsTo);
    transferContractBuilder.setOwnerAddress(bsOwner);
    try {
      Any any = Any.pack(transferContractBuilder.build());
      contractBuilder.setParameter(any);
    } catch (Exception e) {
      return null;
    }
    /*设置备注,交易过期时间等数据*/
    contractBuilder.setType(Transaction.Contract.ContractType.TransferContract);
    transactionBuilder.getRawDataBuilder().addContract(contractBuilder)
        .setTimestamp(System.currentTimeMillis())
        .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000)
        .setData(ByteString.copyFromUtf8("memo"))
        .setScripts(ByteString.copyFromUtf8("scripts"));
    Transaction transaction = transactionBuilder.build();
    Transaction refTransaction = setReference(transaction, newestBlock);
    return refTransaction;
  }

  private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey)
      throws InvalidProtocolBufferException {
    ECKey ecKey = ECKey.fromPrivate(privateKey);
    Transaction transaction1 = Transaction.parseFrom(transaction);
    byte[] rawdata = transaction1.getRawData().toByteArray();
    byte[] hash = Sha256Sm3Hash.hash(rawdata);
    byte[] sign = ecKey.sign(hash).toByteArray();
    return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray();
  }

  private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException {
    return WalletApi.broadcastTransaction(transactionBytes);
  }

  public static void main(String[] args) throws InvalidProtocolBufferException, CancelException {
    String privateStr = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0";
    byte[] privateBytes = ByteArray.fromHexString(privateStr);
    ECKey ecKey = ECKey.fromPrivate(privateBytes);
    byte[] from = ecKey.getAddress();
    byte[] to = WalletApi.decodeFromBase58Check("TN9RRaXkCFtTXRso2GdTZxSxxwufzxLQPP");
    long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 trx = 1000000 Sun
    Transaction transaction = createTransaction(from, to, amount);
    byte[] transactionBytes = transaction.toByteArray();
    byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes);
    boolean result = broadcast(transaction4);

    System.out.println(result);
  }
}

执行完上述代码后,查询此交易信息的结果:

{
    "ret": [
        {
            "contractRet": "SUCCESS"
        }
    ],
    "signature": [
        "ced3929af13ca455fca59088c1a98908897640f6dc7c5746f1a21eb850fb266e6d7996e1984464b0d8a262f991342a1c9e4197311ea3562631f411ffde9abcda00"
    ],
    "txID": "a1185aad04cfa78cb66d5eb3780b8801dde1d49d14aee9e21841194b81a64bd6",
    "raw_data": {
        "data": "6d656d6f",
        "contract": [
            {
                "parameter": {
                    "value": {
                        "amount": 100000000,
                        "owner_address": "41bf97a54f4b829c4e9253b26024b1829e1a3b1120",
                        "to_address": "41859009fd225692b11237a6ffd8fdba2eb7140cca"
                    },
                    "type_url": "type.googleapis.com/protocol.TransferContract"
                },
                "type": "TransferContract"
            }
        ],
        "ref_block_bytes": "c75b",
        "ref_block_hash": "7cfc890c6e4d7a15",
        "expiration": 1583304414000,
        "ref_block_num": 2541403,
        "scripts": "73637269707473",
        "timestamp": 1583268416080
    },
    "raw_data_hex": "0a02c75b18db8e9b0122087cfc890c6e4d7a1540b0a6b0a28a2e52046d656d6f5a68080112640a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412330a1541bf97a54f4b829c4e9253b26024b1829e1a3b1120121541859009fd225692b11237a6ffd8fdba2eb7140cca1880c2d72f62077363726970747370d0949b918a2e"
}

📘

注:

其中的data字段的信息可以用作备注信息(TronScan中会显示data数据),通过Hex转String来获取内容。