Caver -java 에서 once 할때요. js 버전과 로그가 달라요

Caver -java 에서 once 할때요. js 버전과 로그가 다른거 같아 문의 드립니다.

컨트렉트 셋팅하고 웹소켓 연결해서
once로 event 캐치를 했습니다.

js 버전에서는 returnValues 라는 항목이 있어 이벤트시에 인자값들이 출력이 되는데요.

java 버전에서는 returnValues 항목이 없는 상태로 구현이 되어있는거같아서요…

캐치 가능한 방법이 있을가요? 커스텀하게 수정해야 할가요?

안녕하세요.

현재 해당 필드는 caver-java에서 제공되지 않습니다.
다만 아래와 같이 이벤트 로그를 디코딩해서 확인하실 수 있습니다.

    WebSocketService webSocketService = new WebSocketService("ws://{EN url}:8552", false);
    Caver caver = new Caver(webSocketService);
    webSocketService.connect();

    Contract contract = caver.contract.create(jsonObj, contractAddress);

    final LogsNotification[] log = {null};

    EventFilterOptions.IndexedParameter indexedParameter = new EventFilterOptions.IndexedParameter("from", Arrays.asList(keyring.getAddress()));
    EventFilterOptions eventFilterOptions = new EventFilterOptions();
    eventFilterOptions.setFilterOptions(Arrays.asList(indexedParameter));

    ContractEvent eve = contract.getEvents().get("Transfer");
    Disposable disposable = contract.once(eve.getName(), eventFilterOptions, event -> {
        log[0] = event;

        EventValues values = caver.abi.decodeLog(eve.getInputs(), event.getParams().getResult().getData(), event.getParams().getResult().getTopics());
    });

감사합니다

1개의 좋아요

안녕하세요.
답변 감사합니다.
작업해주신 방식대로 해서 로그는 캐치 가능하나 에러가 발생하고 있어 답글 남김니다.

java.lang.ArrayIndexOutOfBoundsException: arraycopy: last source index 32 out of bounds for byte[0]
	at java.base/java.lang.System.arraycopy(Native Method)
	at com.klaytn.caver.abi.TypeDecoder.decodeNumeric(TypeDecoder.java:126)
	at com.klaytn.caver.abi.TypeDecoder.decode(TypeDecoder.java:87)
	at com.klaytn.caver.abi.DefaultFunctionReturnDecoder.build(DefaultFunctionReturnDecoder.java:117)
	at com.klaytn.caver.abi.DefaultFunctionReturnDecoder.decodeFunctionResult(DefaultFunctionReturnDecoder.java:51)
	at com.klaytn.caver.abi.FunctionReturnDecoder.decode(FunctionReturnDecoder.java:59)
	at com.klaytn.caver.abi.ABI.decodeLog(ABI.java:329)
	at com.klaytn.caver.abi.wrapper.ABIWrapper.decodeLog(ABIWrapper.java:251)

에러만 봐서는 말씀드리기 어렵습니다.

위 에러를 재현 가능한 스마트 컨트랙트 bytecode, abi 그리고 이벤트를 발생시키고 캐치하는 자바 소스코드 전달 부탁드립니다.

이벤트 디코딩을 안하면 문제는 없습니다.

하지만 디코딩 하는 순간 위와 같은 오류가 나고 있습니다.

public class KlaytnLog implements Consumer<LogsNotification> {

    private final Caver caver;
    private final Contract contract;

    @Override
    public void accept(LogsNotification logsNotification) {
        try{
            ContractEvent eve = contract.getEvents().get("EventData");
            EventValues values = caver.abi.decodeLog(eve.getInputs(), logsNotification.getParams().getResult().getData(), logsNotification.getParams().getResult().getTopics());
        }catch(Exception e) {
		
        }
    }
}

위의 에러는 특정 데이터를 디코딩하다가 발생하는 에러입니다.
제가 위에 전달드린 데이터로는 해당 에러가 발생하지 않아서 재현할 수 있는 정보 전달을 부탁드린 것 입니다.

아넵.
혹시 그전에 질문이 있습니다.
once 에서 take 1 를 빼면 콜백이 두번 실행 되는대요.
이유가 무엇인지요?

답변 해주실수 없으신지요…

caver-java 에서는 이벤트 구독을 1개 밖에 지원 안하는거 같아서요.

직접 커스텀하게 작업중인데요… 답변 부탁드립니다.

답변이 가능하도록 상세한 설명 부탁드립니다

once 에서 take 1 로직을 제거한 상태로 작업을 하게된다면.

        final Flowable<LogsNotification> events = this.caver.rpc.klay.subscribeFlowable("logs", filter);
        return events.subscribe(callback);

위와 같이 코드를 수정하여 실행해보면
WebSocketClient - Received message
WebSocketService - Processing event

저두개의 로그가 두개씩 찍혀서 올라옵니다…
그래서 이벤트가 한번 발생해도.

EventValues values = caver.abi.decodeLog(eve.getInputs(), logsNotification.getParams().getResult().getData(), logsNotification.getParams().getResult().getTopics());

해당코드에서 두번째 발생하는 로그에서 파싱하다가 오류나네요.

두번 로그가 발생하는거에서 첫번째는 logIndex 0x1 두번째는 logIndex 0x2 라서 정상적인 로그인거같은데. 왜 두개씩 나오는걸가요?

1개 이벤트 발생했을때. 2개가 오고요.
4개 이벤트 발생했을때. 5개 옵니다.

“logIndex”:1
“logIndex”:3
“logIndex”:5
“logIndex”:7
“logIndex”:8

위에가 4개를 이벤트 발생했을때인데요.
2 4 6은 빠진거보니…
klaytn 내부에서 마지막에 있는건 제외를 못하고 callback 으로 올려주는게 아닌가싶어요…

아래처럼 코드를 실행하면 에러 없이 여러 번 이벤트 구독이 가능한데요,
만약 아래 코드로도 안되면 질문자님이 지금 실행중이신 현상이 재현될 수 있도록 코드스니펫 전달 부탁드립니다.

    WebSocketService webSocketService = new WebSocketService("ws://{en url}:8552", false);
    Caver caver = new Caver(webSocketService);
    webSocketService.connect();

    caver.wallet.add(caver.wallet.keyring.createFromPrivateKey("0x{private key}"));
    caver.wallet.add(caver.wallet.keyring.createFromPrivateKey("0x{private key}"));

    Contract contract = caver.contract.create(jsonObj, contractAddress);

    List options = new ArrayList();
    options.add(Arrays.asList(new Address("0x{address in hex}"), new Address("0x{address in hex}")));

    final LogsNotification[] log = {null};

    EventFilterOptions.IndexedParameter indexedParameter = new EventFilterOptions.IndexedParameter("from", Arrays.asList(LUMAN.getAddress()));
    EventFilterOptions eventFilterOptions = new EventFilterOptions();
    eventFilterOptions.setFilterOptions(Arrays.asList(indexedParameter));

    KlayFilter filter = new KlayFilter();
    filter.setAddress(contract.getContractAddress());
    ContractEvent eve = contract.getEvents().get("Transfer");
    eventFilterOptions.setTopicWithFilterOptions(eve);
    filter.setTopics(eventFilterOptions.toKlayFilterTopic());

    final Flowable<LogsNotification> events = caver.rpc.klay.subscribeFlowable("logs", filter);
    Disposable disposable = events.subscribe(event -> {
        log[0] = event;
        EventValues values = caver.abi.decodeLog(eve.getInputs(), event.getParams().getResult().getData(), event.getParams().getResult().getTopics());
        System.out.print(values);
    });

답변 감사합니다.

try {
            caver = new Caver();
            contractAddress = _contractAddress;
            abi = _abi;

            webSocketService = new WebSocketService("wss://api.baobab.klaytn.net:8652", false);

            caver.setCurrentProvider(webSocketService);
            webSocketService.connect();
            contract = caver.contract.create(abi, contractAddress);

            EventFilterOptions eventFilterOptions = new EventFilterOptions();
            KlayFilter filter = new KlayFilter();
            filter.setAddress(contract.getContractAddress());
            ContractEvent eve = contract.getEvents().get("MintData");
            eventFilterOptions.setTopicWithFilterOptions(eve);
            filter.setTopics(eventFilterOptions.toKlayFilterTopic());
            final Flowable<LogsNotification> events = caver.rpc.klay.subscribeFlowable("logs", filter);
            events.subscribe(new KlaytnLog(caver, contract));
        } catch (Exception e) {
            Zlog.error(this.getClass(), "{}",e);
        }

말씀해주신 코드를 약간 수정하여 해당 코드로 진행중인데요.
아래와 같은 에러가 발생합니다.

ERROR 07.26 09:41:47.433 [                 restartedMain] [com.z5corp.mtc.common.KlaytnWebsocket   ]:  40 - {}
java.lang.NullPointerException: null
	at com.klaytn.caver.contract.EventFilterOptions.convertsTopic(EventFilterOptions.java:84)
	at com.klaytn.caver.contract.EventFilterOptions.setTopicWithFilterOptions(EventFilterOptions.java:130)
	at com.z5corp.mtc.common.KlaytnWebsocket.<init>(KlaytnWebsocket.java:62)
	at com.z5corp.mtc.configuration.WebConfiguration.KlaytnWebsocket(WebConfiguration.java:101)
	at com.z5corp.mtc.configuration.WebConfiguration$$EnhancerBySpringCGLIB$$978e559b.CGLIB$KlaytnWebsocket$3(<generated>)
	at com.z5corp.mtc.configuration.WebConfiguration$$EnhancerBySpringCGLIB$$978e559b$$FastClassBySpringCGLIB$$16a4a685.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
	at com.z5corp.mtc.configuration.WebConfiguration$$EnhancerBySpringCGLIB$$978e559b.KlaytnWebsocket(<generated>)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)

eventFilterOptions.setFilterOptions(Arrays.asList(indexedParameter)); 이 부분 추가해 주시기 바랍니다.
만약 없는 경우, 빈 리스트 넘겨서 세팅 해보시기 바랍니다.

1개의 좋아요

친절한 답변 감사합니다!
해결 되었습니다.

caver-java v1.9.1-rc.3 버전에서 contract.subscribe 함수를 지원합니다. 참고 부탁드립니다.

 Contract contract = caver.contract.create(ABI, contractAddress);

 EventFilterOptions options = new EventFilterOptions();
 options.setFilterOptions(Arrays.asList(new EventFilterOptions.IndexedParameter("from", "0x{address}")));
 / /or you can set topic like `options.setTopics(Arrays.asList("topic0", "topic1"));`

 Disposable disposable = contract.subscribe("Transfer", options, (data) -> {});
 disposable.dispose(); // You can unsubscribe the event like below

감사합니다! 지원이 되다니…

그런데…

IndexedParameter 인자 두번째는 list 로 되어있네요.

public IndexedParameter(String indexedParamName, List<Object> filterValue) {
            this.indexedParamName = indexedParamName;
            this.filterValue = filterValue;
        }

네 리스트 형태로 넣어주시면 됩니다.