rchain / rchain

Blockchain (smart contract) platform using CBC-Casper proof of stake + Rholang for concurrent execution.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Transaction api returns error when blockhash is empty

zsluedem opened this issue · comments

commented

What did you expect to see?

404 or 502 in https://observer-exch2.services.mainnet.rchain.coop/api/transactions/

What did you see instead?

2022-06-16 04:56:22,307 [ERROR] [node-runner-27      ] [Routes$ResponseErrorHandler$1]- HTTP API response error
org.lmdbjava.Dbi$BadValueSizeException: Unsupported size of key/DB name/data, or wrong DUPFIXED size (-30781)
        at org.lmdbjava.ResultCodeMapper.checkRc(ResultCodeMapper.java:72)
        at org.lmdbjava.Dbi.get(Dbi.java:239)
        at coop.rchain.store.LmdbKeyValueStore.$anonfun$get$2(LmdbKeyValueStore.scala:62)
        at runToFuture @ coop.rchain.catscontrib.TaskContrib$TaskOps$.unsafeRunSync$extension(taskOps.scala:27)

@zsluedem Please add more info for @hilltracer how to fix this bug.

We also need to log API errors as warnings which will indicate that error is not related to internal node error and node can continue running.

@tgrospic, @zsluedem, I investigated the problem. The exception Dbi.BadValueSizeException occurs in the code

keys.map(x => Option(dbi.get(txn, x)).map(fromBuffer))

when we are passing empty hash as a key.

This is implementation of Dbi.get:

public T get(final Txn<T> txn, final T key) {
    if (SHOULD_CHECK) {
      requireNonNull(txn);
      requireNonNull(key);
      txn.checkReady();
    }
    txn.kv().keyIn(key);
    final int rc = LIB.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn
                               .kv().pointerVal());
    if (rc == MDB_NOTFOUND) {
      return null;
    }
    checkRc(rc);
    return txn.kv().valOut(); // marked as out in LMDB C docs
  }

As you can see in some case (rc == MDB_NOTFOUND) it returns null. I tried to handle Dbi.BadValueSizeException by changing code to

keys.map(x => Try(dbi.get(txn, x)).collect { case null => throw new Exception("Not found") }.toOption.map(fromBuffer))

(note that Try(null).toOption gives Some(null) instead of None)
but it has different behavior and kills node after launch.

So problem simply can be fixed by checking hash is not empty:

(if (blockHash.isEmpty) None.pure[F] else store.get1(blockHash)) >>= { transactionOpt => ...

And the response is "requirement failed: Expected 32 but got 0" which is more clear for users.

What do you think? Is it proper solution or I should investigate why node dies with using Try?

Upd
I was not correct. LmdbKeyValueStore.get should be implemented as

keys.map(
  x =>
    Try(dbi.get(txn, x))
      .collect { case null => throw new Exception("Not found"); case b: ByteBuffer => b }
      .toOption
      .map(fromBuffer)
)

In that case user will see response "requirement failed: Expected 32 but got 0" also but this fix of LmdbKeyValueStore.get better because this method can be used with incorrect arguments in other places.

commented

@stanislavlyalin nice work.