본문 바로가기
블록체인 backEnd/web3j

가스에 대하여

by gun_poo 2023. 3. 15.

가스

Gas는 Ethereum 네트워크에서 특정 작업을 실행하는 데 필요한 계산 노력의 양을 측정하는 단위

 

단위

1 gwei => 0.000000001Eth => 1,000,000,000 wei

 

런던 업그레이드 이전

TotalFee = GasLimit * GasPrice

 

런던 업그레이드 이후

EIP-1559

TotalFee 계산법 : units of gas used(GasLimit) * (baseFee + priorityFee)

baseFee : protocol에서 설정한 값

priorityFee: 사용자가 채굴자에 대한 tip을 설정한 값

 

baseFee
• EIP-1559는 네트워크 조건에 따라 결정되는 기본 요금 매개 변수를 도입
• 모든 트랜잭션이 블록에 포함되기 위해 지불해야 하는 최소 가스 가격

기본료 조정
• 동적 마르코프 과정
• 이전 블록에 사용된 블록 가스에 따른다

블록 가스가 목표치보다 클 경우.
• 다음 블록에 대한 기본 요금이 증가하고, 그 반대도 마찬가지이다

 

EIP-1559의 gasPrice 계산 식

기본 수수료는 거래를 찾는 최종 사용자나 거래를 검증하려는 채굴자가 설정하는 것이 아니라 이더리움 네트워크에 의해 결정된다. 기본 요금은 50% 전체 블록을 목표로 하며 가장 최근에 확인된 블록의 내용을 기반으로 한다. 새 블록이 얼마나 꽉 차 있는지에 따라 기본 요금이 자동으로 증가하거나 감소한다.

예:
마지막 블록이 정확히 50% 가득 찼을 경우 기본 요금은 변경되지 않는다
마지막 블록이 100% 가득 찼을 경우 다음 블록에 대해 기본 요금이 최대 12.5% 인상된다
마지막 블록이 50% 이상 찼지만 100% 미만이면 기본 요금이 12.5% 미만 증가한다.
마지막 블록이 0% 가득 찼을 경우(즉, 비어 있을 경우), 기본 요금은 다음 블록의 최대 12.5%를 감소시킨다
마지막 블록이 0% 이상 50% 미만일 경우 기본 요금이 12.5% 미만으로 감소한다

이 새로운 메커니즘은 거래 수수료를 원활하게 하고 갑작스러운 급등을 방지하기 위한 것이다. 기본료와 관련하여 염두에 두어야 할 가장 중요한 사항은 100% 자동이며 네트워크에서 직접 읽을 수 있다는 것

 

 

위 식을 예로들어 살펴본다면

if a user bids (MaxFee, MaxPriorityFee) = (60, 2)

(1) BaseFee 60을 초과하는 경우
• 트랜잭션이 이 블록에 포함되지 않아야 합니다
• BaseFee 떨어질 때까지 멤풀에서 기다린다
(2) If 58 < BaseFee < 60
• 광부는 이 거래를 포함할지 여부를 선택할 수 있습니다
• 사용자는 광부에게 60 - BaseFee Gwei를 우선 요금으로 지불한다
• 사용자는 가스 당 총 60 Gwei를 지불합니다
(3) 기본료가 58 미만인 경우
• 광부는 이 거래를 포함할지 여부를 선택할 수 있습니다
• 사용자는 광부에게 우선 요금으로 2Gwei를 지불한다
• 사용자는 가스당 기본료 + 2 Gwei를 지불합니다

 

트랜잭션을 예로 들어본다면

 

ex) 조던이 테일러에게 1 이더를 지불해야한다. 거래에서 가스한도는 21,000이고 baseFee는 10gwei이다. 조던은 2gwei 팁을 포함한다.

이는 21,000 * ( 10 + 2) = 252,000gwei를 반환한다.

 

Jordan이 돈을 보내면 1.000252 ETH가 Jordan의 계정에서 차감된다. Taylor는 1.0000 ETH를 받게 된다. Validator는 0.000042 ETH의 팁을 받는다. 0.00021 ETH의 기본 수수료가 소각된다.

 

또한 Jordan은 maxFeePerGas트랜잭션에 대해 최대 수수료( )를 설정할 수도 있다. 최대 수수료와 실제 수수료의 차액은 jordan으로 환불된다. refund = max fee - (base fee + priority fee). Jordan은 트랜잭션 실행에 대해 지불할 최대 금액(max fee)을 설정할 수 있으며 트랜잭션이 실행될 때 기본 수수료 "초과"를 초과 지불하는 것에 대해 걱정하지 않는다.

 

 

 

Transaction 

 

  1. nonce (트랜잭션 번호) - 이전 거래에서 생성된 nonce 값을 기반으로 새로운 nonce 값을 생성합니다. 이 값은 거래의 고유 식별자로 사용된다
  2. gasLimit (가스 한도) - 스마트 컨트랙트를 실행하는 데 필요한 최대 가스의 양입니다. 이 값은 거래가 실행될 때 사용된다
  3. maxPriorityFeePerGas (최대 우선순위 수수료) - 사용자가 거래에서 최대로 지불할 수 있는 우선순위 수수료의 한도이다
  4. maxFeePerGas (최대 수수료) - 사용자가 거래에서 최대로 지불할 수 있는 총 수수료의 한도이다
  5. to (받는 주소) - 거래의 대상이 되는 계정의 주소이다
  6. value (이체할 이더량) - 거래를 통해 이체되는 이더의 양이다
  7. data (스마트 컨트랙트 데이터) - 실행할 스마트 컨트랙트의 데이터이다
  8. chainId (체인 ID) - 거래가 속한 이더리움 네트워크의 체인 ID이다

maxPriorityFeePerGas

baseFee + tip

동일한 블록의 다른 트랜잭션보다 우선적으로 실행되어야 하는 트랜잭션의 경우 경쟁 트랜잭션을 능가하기 위해 더 높은 팁을 사용한다.

기본적으로 이 필드는 선택사항이지만 현재 대부분 네트워크 참여자는 트랜잭션을 성공시키기 위해 최소 2.0gwei 팁이 필요하다고 추정한다. 

혼잡하지 않은 정상적인 네트워크 조건에서 제출되는 "일반적인" 트랜잭션의 경우 2.0gwei에 근접해야한다. 그러나 다음 블록 또는 중요한 거래 혹은 네트워크가 매우 혼잡한 경우 거래 우선순위를 지정하기 위해 더 높은 값이 필요할 수도 있다.

maxFeePerGas

트랜잭션 실행에 대해 지불할 최대 한도

거래가 실행되려면 최대 수수료 > baseFee + tip 가 되어야한다. 거래 발신인은 maxFeeFerGas와 maxPriorityFeePerGas 사이의 차액을 환불 받는다.

maxFee는 사용자가 지정할 수 있지만 자동화를 위해 간단하게 설정해보자면 

MaxFee = (2 * BaseFee) + maxPriorityFeePerGas 

 

예시로 baseFee : 100gwei, tip : 2gwei 라고 했을때 202gwei를 최대값으로 가진다.

위 표를 보면 이 공식은 트랜잭션이 급등하여 6개의 블럭이 모두 다찬 상황까지 커버 할 수 있다. 

 

EIP-1559는

결과적으로 검증자들에게 돌아가는 보상은 줄어들지만 과정에서 가스비가 소각되버리기 때문에 네트워크 사용자, 검증자 모두 간접적으로 이득이 일어날수 있다.

 

 

가스비 지정 자동화

지갑을 사용하지 않는 플랫폼에서 거래를 효율적으로 처리하기 위해서 팁 부분과 gasLimit부분을 자동화 하는 로직을 생각해보자

 

지금 헷갈리는 것은 baseFee와 baseFeePerGas가 다른가 이다. 

확인해보니 baseFeePerGas가 쓰이는것으로 확인된다 

baseFeePerGas는 현재 블럭에서 가져온다

Web3j web3j = Web3j.build(new HttpService("main-net Url"));
        //baseFeePerGas
        EthBlock ethBlock = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true).sendAsync().get();;
        String baseFeePerGasString = ethBlock.getBlock().getBaseFeePerGas();
        byte[] baseFeePerGasBytes = Numeric.hexStringToByteArray(baseFeePerGasString);
        BigInteger baseFeePerGas = new BigInteger(1, baseFeePerGasBytes);
        BigInteger baseFeePerGasToWei = baseFeePerGas.divide(BigInteger.valueOf(1000000000));
        System.out.println(" baseFeePerGas : " + baseFeePerGasToWei);

어제 저녁 기준으로 이더리움 메인넷의 baseFee가 100 정도 되었었다.

오늘 오전 기준으로 baseFee는 17 정도이다. 

 

우선 현재 시점으로 내가 트랜잭션을 네트워크에 전송을 시키게 되면 현재 baseFee를 받아오는 블럭에 저장이 될 수도 있고 다음 블럭에 저장이 될수도있고 팬딩 트랜잭션이 너무 많으면 그 다음 블록에 적재될 수도 있다. 그렇게 되면 일단 생기는 문제는 무엇일까

baseFee는 변한다는 문제이다. 나보다 높은 거래비용을 제시한 트랜잭션들이 앞선 블럭에 적재되었기때문에 나의 트랜잭션이 뒤로 밀리게 되는 것인데 baseFee 는 블럭에 따라 계속 변하게 되고 나는 3블럭 이전의 baseFee를 제시했을 것이다. 

물론 maxFee를 두배로 설정하면 문제는 없을 것이다. 6블럭 최대 100프로가 차지 않는 이상. 

어렵다 어려워 

최적의 방법은 머신러닝을 통해 baseFee 동적 모델링을 하는것이 최선의 방법으로 보인다. 이에 대한 방법은 추후 개발해나가보자.

자체적인 모델링을 구축을 하던지 아니면 api를 쓰던지 해야할것 같다. 

 

우선 이더스캔의 api 에 가스트래커가 있다 메인넷에만 있는거같은데 우선 제공하는 엔드포인트로 함수를 구현해보자면

 public JSONObject getGasOracle () throws Exception {
        String apiEndpoint = "https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=myApiKey";
        URL url = new URL(apiEndpoint);
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        org.json.JSONObject json = new JSONObject(response.toString());
        JSONObject result = json.getJSONObject("result");
        return result;
    }

반환하는 값은 

{"SafeGasPrice":"19",
"ProposeGasPrice":"19",
"LastBlock":"16823853",
"suggestBaseFee":"18.245100141",
"FastGasPrice":"21",
"gasUsedRatio":"0.268590633333333,0.424627066666667,0.698782133333333,0.3857806,0.937766166666667"}

이다 maxPiriorityFeePerGas에 fastGasPrice를 써주면 될거 같다 fastGasprice - baseFee

maxFee는 2배로 주고

 

아까 만들었던 baseFee 추적과 이더스캔의 api를 사용한 값을 비교해보자

@GetMapping("getGasObject")
    public String getGasObject () throws Exception {
        JSONObject gasOracleData = getGasOracle();
        System.out.println(gasOracleData);
        String SafeGasPrice = gasOracleData.getString("SafeGasPrice");
        String ProposeGasPrice = gasOracleData.getString("ProposeGasPrice");
        String LastBlock = gasOracleData.getString("LastBlock");
        String suggestBaseFee = gasOracleData.getString("suggestBaseFee");
        String FastGasPrice = gasOracleData.getString("FastGasPrice");
        String gasUsedRatio = gasOracleData.getString("gasUsedRatio");

        System.out.println("SafeGasPrice" + SafeGasPrice);
        System.out.println("proposeGasPrice " + ProposeGasPrice);
        System.out.println("lastBlock" + LastBlock);
        System.out.println("suggestBaseFee" + suggestBaseFee);
        System.out.println("FastGasPrice" + FastGasPrice);
        System.out.println("gasUsedRatio" + gasUsedRatio);

        Web3j web3j = Web3j.build(new HttpService("https://mainnet.infura.io/v3/myApi"));
        //baseFeePerGas
        EthBlock ethBlock = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true).sendAsync().get();;
        String baseFeePerGasString = ethBlock.getBlock().getBaseFeePerGas();
        byte[] baseFeePerGasBytes = Numeric.hexStringToByteArray(baseFeePerGasString);
        BigInteger baseFeePerGas = new BigInteger(1, baseFeePerGasBytes);
        BigInteger nowBaseFee = baseFeePerGas.divide(BigInteger.valueOf(1000000000));
        System.out.println(" baseFeePerGas : " + nowBaseFee);

//        JSONArray getGasLimit = getGasLimit();
//        System.out.println(getGasLimit);
        return "redirect:/";
    }

에 대한 리턴값들은 이러하다

SafeGasPrice16
proposeGasPrice 16
lastBlock16823996
suggestBaseFee15.052132009
FastGasPrice17
gasUsedRatio0.3815551,0.469327966666667,0.6418416,0.448290433333333,0.374924633333333
baseFeePerGas : 15

현재 baseFeePerGas : 15 이고 안전하게 보내기 위해 팁을 1 더한 safeGasPrice 16 빠르게 보내기 위한 값 팁을 2를 더한 17

 

메인넷에서는 이 방식을 사용하여 쓰면 될것 같고 테스트넷에서는 baseFee를 받아 2를 더해주면 될것같다. maxFee는 두배로 주자.

 

오 더 좋은 api를 찾았다 

엔드포인트  : https://api.blocknative.com/gasprices/blockprices

{
    "system": "ethereum",
    "network": "main",
    "unit": "gwei",
    "maxPrice": 120,
    "currentBlockNumber": 14630321,
    "msSinceLastBlock": 14653,
    "blockPrices": [
        {
            "blockNumber": 14630322,
            "estimatedTransactionCount": 224,
            "baseFeePerGas": 77.233467826,
            "estimatedPrices": [
                {
                    "confidence": 99,
                    "price": 79,
                    "maxPriorityFeePerGas": 2,
                    "maxFeePerGas": 109.93
                },
                {
                    "confidence": 95,
                    "price": 78,
                    "maxPriorityFeePerGas": 1.52,
                    "maxFeePerGas": 109.45
                },
                {
                    "confidence": 90,
                    "price": 78,
                    "maxPriorityFeePerGas": 1.5,
                    "maxFeePerGas": 109.43
                },
                {
                    "confidence": 80,
                    "price": 78,
                    "maxPriorityFeePerGas": 1.27,
                    "maxFeePerGas": 109.2
                },
                {
                    "confidence": 70,
                    "price": 78,
                    "maxPriorityFeePerGas": 1.07,
                    "maxFeePerGas": 109
                }
            ]
        }
    ],
    "estimatedBaseFees": [
        {
            "pending+1": [
                {
                    "confidence": 99,
                    "baseFee": 86.89
                }
            ]
        },
        {
            "pending+2": [
                {
                    "confidence": 99,
                    "baseFee": 97.74
                }
            ]
        },
        {
            "pending+3": [
                {
                    "confidence": 99,
                    "baseFee": 106.19
                }
            ]
        },
        {
            "pending+4": [
                {
                    "confidence": 99,
                    "baseFee": 105.99
                }
            ]
        },
        {
            "pending+5": [
                {
                    "confidence": 99,
                    "baseFee": 107.93
                }
            ]
        }
    ]
}

근데 테스트넷 버전은 없는거 같은데....

가스리밋

public JSONArray getGasLimit () throws Exception {
        String apiEndPoint = "https://api.etherscan.io/api?module=stats&action=dailyavggaslimit&startdate=2019-02-01&enddate=2019-02-28&sort=asc&apikey=myApi";
        URL url = new URL(apiEndPoint);
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        JSONObject json = new JSONObject(response.toString());
        System.out.println(json);
        JSONArray result = json.getJSONArray("result");
        System.out.println(result);
        return result;
    }

이 함수는 끝부분이 잘못됐으나 일단 이더스캔에서 제공하는 유료 옵션이라 사용이 불가하다. 

 

일반적으로 사용하는 방법을 통해 알아보자

트랜잭션을 말아서 쏴봤는데 뭔가 이상하다.

baseFee의 반환 값은 Wei로 반환되는데 7Wei 이다 현재 테스트넷 기준

이는 0.000000007 Gwei 이고

maxPriority는 최소 근사값이 2gwei 여야한다.

maxFee는 두배 준다고했으니 두배로 주자. 

 

테스트넷 기준 EIP - 1559 트랜잭션 sample

 

public String testGasLimit () throws Exception {
        Web3j sepolia = Web3j.build(new HttpService("url"));
        long seploiaChainId = 11155111;
        String contractAddress = "contractAddress";
        String from = "from";
        String fromPrivate = "fromPrivate";
        String url = "1";
        
        EthBlock ethBlock = sepolia.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true).sendAsync().get();;
        String baseFeePerGasString = ethBlock.getBlock().getBaseFeePerGas();
        byte[] baseFeePerGasBytes = Numeric.hexStringToByteArray(baseFeePerGasString);
        BigInteger baseFeePerGas = new BigInteger(1, baseFeePerGasBytes);
        BigInteger nowBaseFee = baseFeePerGas.divide(BigInteger.valueOf(1000000000));
        System.out.println(" baseFeePerGas : " + nowBaseFee);
        
        // testnet gastracker가 없기때문에 하드값설정
        BigInteger maxPriorityFeeHardCode = BigInteger.valueOf(2).multiply(BigInteger.valueOf(1000000000));
        // maxFee는 baseFee와 maxPriorityFee 값보다 항상 커야하므로 두 값을 더한 값으로 지정하려고했으나
        네트워크상황에 따라 팬딩될수 있기때문에 더한값에 2배값을 지정해준다
        BigInteger maxPee = (baseFee.add(maxPriorityFeeHardCode)).multiply(BigInteger.valueOf(2));
        
        BigInteger value = BigInteger.ZERO;
        
        //my Contract Function 
         Function mintFunction = new Function("mint",
                Arrays.asList(new Utf8String(url))
                , Collections.emptyList()
        );
        
        String mintEncodedFuntion = FunctionEncoder.encode(mintFunction);

        org.web3j.protocol.core.methods.request.Transaction transaction =
                org.web3j.protocol.core.methods.request.Transaction.createFunctionCallTransaction(
                        from,
                        checkNonce(sepolia, from),
                        null,
                        null,
                        contractAddress,
                        mintEncodedFuntion
                );
                
        // gas Limit estimate
        EthEstimateGas ethEstimateGas = sepolia.ethEstimateGas(transaction).send();
        BigInteger gasLimited = ethEstimateGas.getAmountUsed();

        System.out.println("gasLimited  " + gasLimited);
        
        RawTransaction priviousMintTx = RawTransaction.createTransaction(
                seploiaChainId,
                nonce,
                gasLimited,
                contractAddress,
                value,
                mintEncodedFuntion,
                maxPriorityFeeHardCode,
                maxPee
        );
        
        byte[] mintSignedMessage = TransactionEncoder.signMessage(priviousMintTx, seploiaChainId, credentials(fromPrivate));
        String mintHexValue = Numeric.toHexString(mintSignedMessage);
        System.out.println("hexValue" + mintHexValue);
        EthSendTransaction mintEthSendTransaction = sepolia.ethSendRawTransaction(mintHexValue).send();
        String mintTransactionHash = mintEthSendTransaction.getTransactionHash();
        System.out.println("Tx hash : " + mintTransactionHash);
        while (true) {
            EthGetTransactionReceipt mintTransactionReceipt = sepolia
                    .ethGetTransactionReceipt(mintTransactionHash)
                    .send();
            if (mintTransactionReceipt.getResult() != null) {
                net.sf.json.JSONObject receipt = net.sf.json.JSONObject.fromObject(mintTransactionReceipt);
                System.out.println("Mint Receipt" + receipt);
                net.sf.json.JSONObject receiptResult = net.sf.json.JSONObject.fromObject(receipt.getJSONObject("result"));
                System.out.println("Result :" + receiptResult);
                net.sf.json.JSONObject receiptLogs = (net.sf.json.JSONObject) net.sf.json.JSONObject.fromObject(receiptResult).getJSONArray("logs").get(0);
                String tokenId = String.valueOf(Long.decode(receiptLogs.getJSONArray("topics").get(3).toString()));
                System.out.println("TokenId" + tokenId);
                break;
            }
            Thread.sleep(15000);
        }
        return "redirect:/";
    }

 

이더 스캔 확인

테스트넷을 다만들었으니 메인넷을 가스트래커를 이용해서 만들어보자.

 

그전에 코드를 모듈화하는 작업을 해놔야겠다. 

'블록체인 backEnd > web3j' 카테고리의 다른 글

maxFeePerGas, maxPriorityFeePerGas 추정기  (0) 2023.03.18
transaction 모듈화  (0) 2023.03.15
java web3j 컴파일 및 wrappers  (0) 2023.01.25
mac os web3j 설치하기  (0) 2023.01.25

댓글