{code: -32000, message: "undefined tx type"} 에러 질문입니다

안녕하세요. VALUE_TRANSFER 타입의 트랜잭션을 보내려고 하는데 노드에서 undefined tx type라는 에러 응답이 와서 문의드립니다.

unsignedTx를 RLP 문자열 트랜잭션로 변환한 이후에 사인 함수를 호출해서 sig를 뽑았습니다.
이후에 caver-js에서 제공하는 getRawTransactionWithSignatures를 호출해서 rawTransaction을 만들고 이 값을 노드로 broadcast 했는데요.

혹시 undefined tx type이 에러가 왜 발생하는지 알 수 있을까요? 감사합니다.

import {inputCallFormatter} from 'caver-js/packages/caver-core-helpers/src/formatters.js';
import {encodeRLPByTxType} from 'caver-js/packages/caver-klay/caver-klay-accounts/src/makeRawTransaction.js';
const Hash = require('eth-lib/lib/hash');

// caver-js 참조
const coverInitialTxValue = tx => {
  if (typeof tx !== 'object') throw new Error('Invalid transaction');
  if (
    !tx.senderRawTransaction &&
    (!tx.type ||
      tx.type === 'LEGACY' ||
      tx.type.includes('SMART_CONTRACT_DEPLOY'))
  ) {
    tx.to = tx.to || '0x';
    tx.data = caver.utils.addHexPrefix(tx.data || '0x');
  }
  tx.chainId = caver.utils.numberToHex(tx.chainId);
  return tx;
};

const unsignedTx = {
  chainId: 1001 
  from: "0x0753675ef3be557a6939dd94ec063b166f0b23e8",
   gasLimit: "0x2dc6c0" ,
  gasPrice: "0xae9f7bcc00" ,
  nonce: "0x00" ,
  to: "0x0753675ef3be557a6939dd94ec063b166f0b23e8" ,
  type: "VALUE_TRANSFER" ,
  value: "0x2386f26fc10000"
}

const txObject = inputCallFormatter(unsignedTx);
const transaction = coverInitialTxValue(txObject);
const rlpEncoded = encodeRLPByTxType(transaction);

const messageHash = Hash.keccak256(serializedTx)

const {v,r,s} = await _sign(messageHash)

const caver = new Caver();
const caverSignResult = await caver.klay.accounts.getRawTransactionWithSignatures({
            ...unsignedTx,
            signatures: [[v, r, s]],
          });
const rawTx = caverSignResult.rawTransaction;

_broadcastTx(rawTx); => {code: -32000, message: "undefined tx type"}

안녕하세요

혹시 caver-js 라이브러리를 활용하지 않고 순수하게

Value Transfer 트랜잭션을 날려보고싶으신걸까요?

caver-js를 활용하여 Value Transfer 트랜잭션은

아래와 같이 날려보실 수 있습니다.

const account = caver.klay.accounts.privateKeyToAccount(PRIVATE_KEY);
caver.klay.accounts.wallet.add(account);

async function bootstrap() {
  const tx = await caver.klay.sendTransaction({
    type: 'VALUE_TRANSFER',
    from: account.address,
    to: '0x0000000000000000000000000000000000000000',
    value: caver.utils.toPeb('1', 'KLAY'),
    gas: '300000',
  });

  console.log(tx);
}

bootstrap();

일부 매뉴얼하게 변경한다면

아래와 같이 시도해볼 수 있을것같습니다.

처음부터 끝까지 만들어보시고 싶으신경우

RIP Encode, Sign 부분까지 모두 구현하시면 될것같습니다.

import fetch from 'cross-fetch';
import { Address, Transaction } from 'micro-eth-signer';

async function bootstrap() {
  const address = Address.fromPrivateKey(process.env.PRIVATE_KEY);
  const tx = new Transaction({
    chainId: '1001',
    nonce: '13',
    gasPrice: '750000000000',
    gasLimit: '300000',
    value: '1000000000000000000',
    to: '0x0000000000000000000000000000000000000000',
    data: '',
    r: '',
    s: '',
  });

  const signedTx = await tx.sign(process.env.PRIVATE_KEY);

  const payload = await fetch('https://api.baobab.klaytn.net:8651/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      id: 1,
      jsonrpc: '2.0',
      method: 'klay_sendRawTransaction',
      params: [signedTx.hex],
    }),
  })

  console.log(await payload.json());
}

bootstrap();

Sign 모듈만 별도로 처리하고자 합니다.
=> const {v,r,s} = await _sign(messageHash)

legacy type으로 보내면 노드에서 잘 처리되는데 이상하게 type이 들어가면 에러가 발생합니다.

RIP Encode 실행하는 과정에서의 문제일까요? caver-js 코드 그대로 불러와서 호출한거인데요.

아니면 unsignedTx의 문제인지 위에 상황이 어떤 경우에 발생하는지 모르겠네요.
혹시 undefined tx type 에러를 노드에서 어떤 경우에 보내는지는 알 수 있을까요?

안녕하세요 답변이 많이 늦어졌네요

정확한 이슈를 잘모르겠는데 Legacy Tx와 ValueTransfer Tx의 차이가

ValueTransfer는 RLP 인코딩시 포함시키는 인자와 Prefix로 "0x08"이라는 값이 들어갑니다

아래 유틸 라이브러리를 제외하고 직접 순수하게 작성해보았습니다.

import * as sha3 from '@noble/hashes/sha3';
import * as utils from '@noble/hashes/utils';
import * as secp256k1 from '@noble/secp256k1';
import RLP from 'rlp';
import fetch from 'cross-fetch';

function pad0x(s: string): string {
  return s.length % 2 === 0 ? s : `0${s}`;
}

function trim0x(s: string): string {
  return s.replace(/^0x/i, '');
}

function restore0x(s: string): string {
  return s.startsWith('0x') ? s : `0x${s}`;
}

function numberToHex(n: number | bigint): string {
  return restore0x(pad0x(n.toString(16)));
}

class Account {
  public address: string;
  public privateKey: string;

  public static checksum(address: string): string {
    const trimmed = trim0x(address);
    if (trimmed.length !== 40) {
      throw new Error('Invalid address');
    }

    const hash = trim0x(utils.bytesToHex(sha3.keccak_256(trimmed)));

    let checksum = '';

    for (let i = 0; i < trimmed.length; i++) {
      const curr = parseInt(hash[i], 16);
      const character = trimmed[i];

      if (curr > 7) {
        checksum += character.toUpperCase();
      } else {
        checksum += character;
      }
    }

    return restore0x(checksum);
  }

  public static fromPrivateKey(privateKey: string): Account {
    const buf = utils.hexToBytes(trim0x(privateKey));
    const pub = secp256k1.getPublicKey(buf);
    const address = utils
      .bytesToHex(sha3.keccak_256(pub.slice(1, 65)))
      .slice(24);

    const account = new Account();
    account.privateKey = privateKey;
    account.address = restore0x(address);

    return account;
  }
}

enum TransactionType {
  ValueTransfer = 8,
}

interface RawValueTransfer {
  nonce: string;
  gasPrice: string;
  gasLimit: string;
  chainId: string;
  to: string;
  value: string;
  from: string;
}

class ValueTransfer {
  nonce: bigint;
  gasPrice: bigint;
  gasLimit: bigint;
  chainId: bigint;
  to: string;
  value: bigint;
  from: string;

  public static fromObject(raw: RawValueTransfer) {
    const tx = new ValueTransfer();
    tx.nonce = BigInt(raw.nonce);
    tx.gasPrice = BigInt(raw.gasPrice);
    tx.gasLimit = BigInt(raw.gasLimit);
    tx.chainId = BigInt(raw.chainId);
    tx.to = raw.to.toLowerCase();
    tx.value = BigInt(raw.value);
    tx.from = raw.from.toLowerCase();

    return tx;
  }

  public serializeForSignature() {
    return sha3.keccak_256(
      RLP.encode([
        RLP.encode([
          numberToHex(TransactionType.ValueTransfer),
          numberToHex(this.nonce),
          numberToHex(this.gasPrice),
          numberToHex(this.gasLimit),
          this.to.toLowerCase(),
          numberToHex(this.value),
          this.from.toLowerCase(),
        ]),
        numberToHex(this.chainId),
        '0x',
        '0x',
      ]),
    );
  }

  public async sign(account: Account) {
    const [buf, recov] = await secp256k1.sign(
      this.serializeForSignature(),
      trim0x(account.privateKey),
      {
        recovered: true,
      },
    );

    const signature = secp256k1.Signature.fromHex(buf);
    const chainId = Number(this.chainId.toString());
    const v = recov + (chainId * 2 + 35);

    return restore0x(
      (
        numberToHex(TransactionType.ValueTransfer) +
        utils.bytesToHex(
          RLP.encode([
            numberToHex(this.nonce),
            numberToHex(this.gasPrice),
            numberToHex(this.gasLimit),
            this.to.toLowerCase(),
            numberToHex(this.value),
            this.from.toLowerCase(),
            [[v, signature.r, signature.s].map((x) => numberToHex(x))],
          ]),
        )
      ).slice(2),
    );
  }
}

async function bootstrap() {
  const sender = Account.fromPrivateKey(process.env.PRIVATE_KEY);
  const tx = ValueTransfer.fromObject({
    nonce: '16',
    gasPrice: '250000000000',
    gasLimit: '300000',
    chainId: '1001',
    to: '0x0000000000000000000000000000000000000000',
    value: '1000000000000000000',
    from: sender.address,
  });

  const message = await tx.sign(sender);

  const payload = await fetch('https://api.baobab.klaytn.net:8651/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      id: 1,
      jsonrpc: '2.0',
      method: 'klay_sendRawTransaction',
      params: [message],
    }),
  });

  console.log(await payload.json());
}

bootstrap().then(() => {
  process.exit(0);
});

ValueTransfer 타입밖에 없는데

Abstract Transaction 클래스를 만드시고 상속받아서 여러가지 트랜잭션을 구현해볼 수 있을것 같습니다.

Dependencies

"dependencies": {
  "@noble/hashes": "^1.0.0",
  "@noble/secp256k1": "^1.5.5",
  "cross-fetch": "^3.1.5",
  "rlp": "^3.0.0"
}
1 Like