Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Research transaction logging #37

Open
junha1 opened this issue Jul 24, 2022 · 4 comments
Open

Research transaction logging #37

junha1 opened this issue Jul 24, 2022 · 4 comments
Assignees

Comments

@junha1
Copy link
Member

junha1 commented Jul 24, 2022

No description provided.

@kmlee307
Copy link

kmlee307 commented Jul 24, 2022

Ethereum Transaction Receipt

트랜잭션이 채굴될 때까지는 null을 반환하기 때문이 트랜잭션의 상태를 tracking하는데 사용될 수 있다.
보통 contract creation transaction의 contract address를 얻기 위해 사용된다.

The structure of transaction receipt

Transaction receipt의 구조는 다음과 같다.
Parameters
Data, 32bytes - hash of a transaction
Returns
transactionHash: DATA, 32 Bytes - hash of the transaction.
transactionIndex: QUANTITY - integer of the transactions index position in the block.
blockHash: DATA, 32 Bytes - hash of the block where this transaction was in.
blockNumber: QUANTITY - block number where this transaction was in.
from: DATA, 20 Bytes - address of the sender.
to: DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction.
cumulativeGasUsed: QUANTITY - The total amount of gas used when this transaction was executed in the block.
effectiveGasPrice: QUANTITY - the price per gas at the time of your transaction, so the total gas cost of your transaction is effectiveGasPrice * gasUsed
gasUsed: QUANTITY - The amount of gas used by this specific transaction alone.
contractAddress: DATA, 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.
logs: Array - Array of log objects, which this transaction generated.
logsBloom: DATA, 256 Bytes - Bloom filter for light clients to quickly retrieve related logs.
It also returns either:
root : DATA 32 bytes of post-transaction stateroot (pre Byzantium)
status: QUANTITY either 1 (success) or 0 (failure)

A simple use of receipt is to find out a new contract's contractAddress
A more advanced used for receipt is with Proving the Existenc of Logs to the Blockchain.
The status indicates if the transaction succeeded or not.

[ref]

@kmlee307
Copy link

kmlee307 commented Jul 24, 2022

Near Transaction Receipt

Transaction status

query result는 다음의 것들을 보여준다.

  • the overall status of the transaction
  • the outcomes of the transaction
  • the outcome of the receipt generated by this transaction
Example of the query results
{
  "status": { "SuccessValue": "" },
  "transaction": {
    "actions": [
      { "Transfer": { "deposit": "50000000000000000000000000" } }
    ],
    "hash": "EL9cEcoiF1ThH1HXrdE5LBuJKzSe6dRr7tia61fohPrP",
    "nonce": 51,
    "public_key": "ed25519:5zset1JX4qp4PcR3N9KDSY6ATdgkrbBW5wFBGWC4ZjnU",
    "receiver_id": "transfer-vote.near",
    "signature": "ed25519:37rcwcjDBWWAaaRYCazHY72sfDbmudYvtmEBHMFmhYEfWD3mbrgrtYs5nVh9gzRUESELRDET9g72LnAD2BWdSgKu",
    "signer_id": "near"
  },
  "transaction_outcome": {
    "block_hash": "dvwSabiWzRjfQamZCEMeguxxXL4885JGU87xfjoPWR2",
    "id": "EL9cEcoiF1ThH1HXrdE5LBuJKzSe6dRr7tia61fohPrP",
    "outcome": {
      "executor_id": "near",
      "gas_burnt": 223182562500,
      "logs": [],
      "metadata": { "gas_profile": null, "version": 1 },
      "receipt_ids": [
        "6LrHPazG3DTcKkd4TjqbgajqmbcAfyoTG383Cft5SZ5Y"
      ],
      "status": {
        "SuccessReceiptId": "6LrHPazG3DTcKkd4TjqbgajqmbcAfyoTG383Cft5SZ5Y"
      },
      "tokens_burnt": "22318256250000000000"
    },
    "proof": []
  },
  "receipts_outcome": [
    {
      "block_hash": "6evPKFQRw1E3gH9L1d59mz7GahsbnqsdYwcZQo8hpFQB",
      "id": "6LrHPazG3DTcKkd4TjqbgajqmbcAfyoTG383Cft5SZ5Y",
      "outcome": {
        "executor_id": "transfer-vote.near",
        "gas_burnt": 223182562500,
        "logs": [],
        "metadata": { "gas_profile": null, "version": 1 },
        "receipt_ids": [
          "7NMpF9ZGwSj48bpvJK2xVobJkTasEkakazTKi2zotHR4"
        ],
        "status": { "SuccessValue": "" },
        "tokens_burnt": "22318256250000000000"
      },
      "proof": []
    },
    {
      "block_hash": "Gm6TFS1ZxmA45itVj8a7vE8yJF8V5hXeNF1EhEVr7GVS",
      "id": "7NMpF9ZGwSj48bpvJK2xVobJkTasEkakazTKi2zotHR4",
      "outcome": {
        "executor_id": "near",
        "gas_burnt": 0,
        "logs": [],
        "metadata": { "gas_profile": null, "version": 1 },
        "receipt_ids": [],
        "status": { "SuccessValue": "" },
        "tokens_burnt": "0"
      },
      "proof": []
    }
  ]
}

각각의 status가 의미하는 바는 다음과 같다.

  • the top level : 모든 transaction의 action이 성공적으로 실행되었는지 나타낸다.
  • under transaction_outcome : transaction이 성공적으로 receipt로 바뀌었는지 나타낸다.
  • under receipts_outcome : receipt가 성공적으로 실행되었는지를 나타낸다.

status가 가지는 값은 다음 4가지 중 하나이다.

  • status: { SuccessValue: 'val or empty'} : receipt 또는 transaction이 성공적으로 실행됨. receipt의 결과 값을 반환하고 다른 경우 empty 반환
  • status: { SuccessReceiptId: 'id_of_generated_receipt' } : transaction이 성공적으로 receipt로 변환되거나 receipt가 성공적으로 처리되어 다른 receipt를 생성함. value of key는 새로 생성된 receipt의 id.
  • status: { Failure: {} } : transaction 또는 receipt가 실패함. value에 오류 원인 반환
  • status: { Unknown: '' } : transaction 또는 receipt가 아직 처리되지 않음.

RPC API

RPC API를 통해 send transaction/query status를 할 수 있다.

Send transaction (async)
: sends a transaction and immediately returns transaction hash

  • method : broadcast_tx_async
  • params : [SignedTransaction encoded in base64]

Send transaction (await)
: sends a transaction and waits until transaction is fully complete

  • method : broadcast_tx_commit
  • params : [SignedTransaction encoded in base64]

Transaction status
: queries status of transaction by hash and returns the final transaction result

  • method : tx
  • params
    • transaction hash
    • sender account id

외에도 Transaction Status with Receipts, Receipt by ID query 존재

Error Handling

API request가 실패했을 때, RPC server는 structured error response를 반환한다. 따라서 client code가 모든 error case를 handling할 수 있도록 한다. JSON-RPC 에러로 verror convention를 따른다.

{
    "error": {
        "name": <ERROR_TYPE>,
        "cause": {
            "info": {..},
            "name": <ERROR_CAUSE>
        },
        "code": -32000,
        "data": String,
        "message": "Server error",
    },
    "id": "dontcare",
    "jsonrpc": "2.0"
}

각각의 query에 대한 error case는 참고자료에서 확인할 수 있다.

[NEAR Docs-RPC Endpoints-transaction]
[NEAR Docs-transaction-receipt]
[NEAR Nomicons-Receipt]

@kmlee307
Copy link

kmlee307 commented Jul 26, 2022

Log

Macro near_sdk::log

macro_rules! log {
    ($arg:expr) => { ... };
    ($($arg:tt)*) => { ... };
}

env::log_str 을 통한 log message marco. std::format macro와 유사하게 사용된다. 하지만 std::format과 달리 env::log_str는 utf8 bytes의 로그를 남긴다. log message는 체인에 기록된다.

panic

pub fn panic_str(message: &str) -> !
UTF-8 type의 메시지와 함께 프로그램을 종료한다.

log for debug

Log a debug message to the console
The log crate provides logging utilities. env_logger crate는 environment variable을 통해 로그를 구성한다. log::debug! macro는 다른 std:;fmt formatted strings처럼 작동한다.

fn execute_query(query: &str) {
    log::debug!("Executing query: {}", query);
}

fn main() {
    env_logger::init();

    execute_query("DROP TABLE students");
}

위의 코드는 아무것도 출력하지 않지만 다음 커멘드를 통해 log level을 debug level로 낮추어 사용할 수 있다
$ RUST_LOG=debug cargo run

Log an error message to the console
적절한 level에서 다루기 위해 log::error! macro를 사용한다.

fn execute_query(_query: &str) -> Result<(), &'static str> {
    Err("I'm afraid I can't do that")
}

fn main() {
    env_logger::init();

    let response = execute_query("DROP TABLE students");
    if let Err(err) = response {
        log::error!("Failed to execute query: {}", err);
    }
}

Log to stdout instead of stderr
log output의 target을 지정하기 위해 Builder::target를 이용해 custom logger configuration을 만들 수 있다.


use env_logger::{Builder, Target};

fn main() {
    Builder::new()
        .target(Target::Stdout)
        .init();

    log::error!("This error has been printed to Stdout");
}

Log 확인

  1. log 초기화
    env_logger::init()

  2. logging

  3. log 확인
    RUST_LOG=<level> cargo run --bin env_logger

[Crate near_sdk-Macros-log]
[Rust Cookbook-Log Messages]
[Rust logging samples]

@kmlee307
Copy link

kmlee307 commented Jul 29, 2022

Log at VM context/full node

near_sdk::env::log_str

pub fn log_str(message: &str)
Logs the string message message. This message is stored on chain.

위 docs에서 연결된 source 파일을 확인해보면
line 693-698

pub fn log_str(message: &str) {
    #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
    eprintln!("{}", message);

    unsafe { sys::log_utf8(message.len() as _, message.as_ptr() as _) }
}

동일 파일의 line 17에서
use near_sys as sys;

Macro std::eprintln

macro_rules! eprintln {
    () => { ... };
    ($($arg:tt)*) => { ... };
}

줄바꿈과 함께 표준 오류로 인쇄한다.
println! 과의 차이점은 io::stdout 대신 io::stderr로 출력한다.

std::io::stderr

pub fn stderr() -> Stderr
Constructs a new handle to the standard error of the current process.
This handle is not buffered.

near_sys::log_utf8

pub unsafe extern "C" fn log_utf8(len: u64, ptr: u64)

Real TIme Events

개발 과정에서 실시간으로 특정 event를 tracking한다. contract가 event 발생을 실시간으로 알려주기 위해 standard events format인 NEP-297을 이용한다. 공공적인 event에 대하여 websocket을 이용하여 실시간적으로 tracking 서비스를 빌드한다.

NEP-297-Events

contarct의 표준적인 log capability를 사용한다. EVENT_JSON:prefix로 시작하는 log entries이다. JSON string은 다음과 같은 interface를 따른다.

interface EventLogData {
    standard: string, // name of standard, e.g. nep171
    version: string, // e.g. 1.0.0
    event: string, // type of the event, e.g. nft_mint
    data?: unknown, // event data defined in the nep171
}

Thus, to emit an event, you only need to log a string following the rules above. Here is a barebones example using Rust SDK near_sdk::log! macro (security note: prefer using serde_json or alternatives to serialize the JSON string to avoid potential injections and corrupted events):

use near_sdk::log;

// ...
log!(
    r#"EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "YYY", "data": {"token_id": "{}"}}"#,
    token_id
);
// ...

Listening to Event

mainnet의 event를 확인하기 위해 보안된 websocket wss://events.near.stream/ws에 연결하면 된다. As first message you will need to pass an object stating the type of events you want to filter for, and a limit if necessary. For example, here we filter for the nft_mint event in the nft.nearapps.near account.

{
  secret: "ohyeahnftsss",
  filter: [{
    "account_id": "nft.nearapps.near",
    "status": "SUCCESS",
    "event": {
      "standard": "nep171",
      "event": "nft_mint",
    }
  }],
  fetch_past_events: 20,
}

[Module " sdk-core/assemply/contract]
[near_sdk::env::log_str]
[near_sdk::env::log_str-source]
[Macro std::eprintln]
[std::io::stderr]
[near_sys::log_utf8]
[NEAR-DOCS-Real TIme Events]
[Events Fromat-NEP-297]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants