Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Latest commit

 

History

History
244 lines (201 loc) · 8.65 KB

breakingChanges.md

File metadata and controls

244 lines (201 loc) · 8.65 KB

1. Newly created transaction type

After this PR merged, new transaction type will be supported.

Newly created transaction types are following:

  1. ValueTransfer type transaction
  2. AccountCreation type transaction

1. ValueTransfer type transaction To send "ValueTransfer" type transaction, you should add type: 'VALUE_TRANSFER' key-value pair to transaction object.

{
  type: 'VALUE_TRANSFER',
  from: '...',
  to: '...',
  ...
}

The RLP encoding for this transaction is quite different from legacy transaction.

i) Value transfer transaction RLP encoding

RLP.encode([
        VALUE_TRANFSER_TYPE_TAG, // '0x10'
        [
          Bytes.fromNat(transaction.nonce),
          Bytes.fromNat(transaction.gasPrice),
          Bytes.fromNat(transaction.gas),
          transaction.to.toLowerCase(),
          Bytes.fromNat(transaction.value),
          transaction.from.toLowerCase(),
        ],
        Bytes.fromNat(transaction.chainId || "0x1"),
        "0x",
        "0x"
      ])

ii) Legacy transaction RLP encoding

RLP.encode([
        Bytes.fromNat(transaction.nonce),
        Bytes.fromNat(transaction.gasPrice),
        Bytes.fromNat(transaction.gas),
        transaction.to.toLowerCase(),
        Bytes.fromNat(transaction.value),
        transaction.data,
        Bytes.fromNat(transaction.chainId || "0x1"),
        "0x",
        "0x",
      ])

2. AccountCreation type transaction
To send "AccountCreation" type transaction, you should add

type: 'ACCOUNT_CREATION',
to: ..., (new address which would be newly created)
publicKey: ..., (optional)
humanReadable: ..., (optional)

key-value pairs to transaction object.

The RLP encoding for this transaction is quite different from legacy transaction.

i) Account creation transaction RLP encoding

RLP.encode([
          ACCOUNT_CREATION_TYPE_TAG,
          [
            Bytes.fromNat(transaction.nonce),
            Bytes.fromNat(transaction.gasPrice),
            Bytes.fromNat(transaction.gas),
            transaction.to.toLowerCase(),
            Bytes.fromNat(transaction.value),
            transaction.from.toLowerCase(),
          ],
          Bytes.fromNat(transaction.transaction.humanReadable === true ? '0x1' : '0x0'),
          accountKey,
          Bytes.fromNat(transaction.chainId || "0x1"),
          "0x",
          "0x",
        ])

accountKey is the value generated as following: If there is no publicKey field in the transaction object, the account key type is "ACCOUNT_NIL", on the other hand, "ACCOUNT_PUBLIC".

      let accountKey

      const xyPoints = transaction.publicKey && utils.xyPointFromPublicKey(transaction.publicKey)

      // 1. Check Account key type
      if (xyPoints !== undefined && xyPoints.length) { // ACCOUNT_KEY_PUBLIC_TAG
        const [pubX, pubY] = xyPoints
        accountKey = ACCOUNT_KEY_PUBLIC_TAG + RLP.encode([pubX, pubY]).slice(2)
      } else { // ACCOUNT_KEY_NIL_TAG
        accountKey = ACCOUNT_KEY_NIL_TAG
      }

ii) Legacy transaction RLP encoding

RLP.encode([
        Bytes.fromNat(transaction.nonce),
        Bytes.fromNat(transaction.gasPrice),
        Bytes.fromNat(transaction.gas),
        transaction.to.toLowerCase(),
        Bytes.fromNat(transaction.value),
        transaction.data,
        Bytes.fromNat(transaction.chainId || "0x1"),
        "0x",
        "0x",
      ])

"AccountCreation" type transaction has 4 cases:

  1. has publicKey, humanReadable: true create human readable account and connect the given public key to the account. The address for this account is the value of to in transaction object.

  2. has publicKey, humanReadable: false create regular account and connect the given public key to the account. The address for this account is the value of to in transaction object.

// Possible, but useless cases. 3) hasn't publicKey, humanReadable: false create regular account without connecting public key to the account. If you don't have a private key for the account address, you can't withdraw balance from the account. However, if you have the private key for it, there is no reason to send "AccountCreation" type transaction.

  1. hasn't publicKey, humanReadable: true create human readable account without connecting public key to the account. Maybe this transaction is useless in many cases.

2. Breaking changes

  1. Must have from field in transaction

i) There was no need to contain from field to a transaction since it can be recovered from v, r, s signatures.
However, after account type is newly created, there is no way to find address who signed this transaction by only recovering signatures. That's the reason why we must not omit from field for sending transaction.

const fromOmittedTxObject = _.omit(tx, 'from')

So above line was removed.

ii) Should apply inputAddressFormatter for from field.

if (options.from) {
  options.from = inputAddressFormatter(options.from)
}

So above line was created.

  1. Must support human-readable string
    i) Should parse human-readable string to hex address.

'toshi' utf8 string can be changed to hex string 0x746f736869. However for the hex string to be valid address, it should be length of 20 bytes which can be generated by adding '0' padding to right. As a result, 'toshi' can be changed to 0x746f736869000000000000000000000000000000 (20bytes).

const humanReadableStringToHexAddress = (humanReadableString) => {
  const addressLength = 40 // 20 bytes
  let hex = utf8ToHex(humanReadableString)
  if (hex.length > 40 + 2) throw Error(`Invalid human readable account length! It should be less than 20 bytes: ${hex}`)
  hex = rightPad(hex, addressLength)
  return hex
}

So above line was created.

ii) Should be possible to add humanreadable account to accounts.wallet instance.
caver.klay.accounts.wallet.add function can have one more argument than before. Since human-readable address can't be achieved from private key from now on. So to add human-readable address with private key, You should use the API like below:
caver.klay.accounts.wallet.add(privateKey, 'toshi')
This will map 'toshi' address with the given private key.

Wallet.prototype.add = function (account, humanReadableString) {
  // ...

  if (humanReadableString) {
    // utils.humanReadableStringToHexAddress('toshi') === '0x746f736869000000000000000000000000000000'
    const humanReadableAddress = utils.humanReadableStringToHexAddress(humanReadableString)
    account.address = humanReadableAddress

    const accountAlreadyExists = !!this[humanReadableAddress]

    if (accountAlreadyExists) return this[humanReadableAddress]

    account.index = this._findSafeIndex()
    this[account.index] = account

    this[humanReadableString] = account // this['toshi']
    this[humanReadableAddress] = account // this['0x746f736869000000000000000000000000000000']
    this[humanReadableAddress.toLowerCase()] = account
    this.length++
  }

  // ...
}

So above line was created.

iii) Removing, clearing wallet instance should consider human-readable address. It was possible to remove a wallet instance by index or address by calling caver.klay.accounts.wallet.remove(index), caver.klay.accounts.wallet.clear(). There were only 3 indexes for the wallet instance: index, address, address.toLowerCase(). After human-readable address features added, we should have one more index humanReadableString.
For example, caver.klay.accounts.wallet.add(privateKey, 'toshi') should make an 4 indexes:

caver.klay.accounts.wallet[0]
caver.klay.accounts.wallet['0x746f736869000000000000000000000000000000']
caver.klay.accounts.wallet['0x746f736869000000000000000000000000000000'] // lowercase
caver.klay.accounts.wallet['toshi']

which means we should consider humanReadableString index for removing and clearing also.

// humanreadable string
const humanReadableString = utils.hexToUtf8(account.address)
if (this[humanReadableString]) {
  this[humanReadableString].privateKey = null
  delete this[humanReadableString]
}

So above line was created.

iv) Validating for address should consider human-readable address.

var inputAddressFormatter = function (address) {

    var iban = new utils.Iban(address);
    if (iban.isValid() && iban.isDirect()) {
        return iban.toAddress().toLowerCase();
    } else if (utils.isAddress(address)) {
        return '0x' + address.toLowerCase().replace('0x','');
    } else if (utils.isHumanReadableString(address)) { // humanreadable address
        return utils.humanReadableStringToHexAddress(address)
    }
    throw new Error('Provided address "'+ address +'" is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can\'t be converted.');
};

So above line was created.