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

add keccak256 native #1354

Merged
merged 1 commit into from
Mar 27, 2024
Merged

add keccak256 native #1354

merged 1 commit into from
Mar 27, 2024

Conversation

chessai
Copy link
Contributor

@chessai chessai commented Mar 19, 2024

Tests

Unit tests, Haskell:

  • tests/Keccak256Spec.hs
❯ cabal run test:hspec -- --match=Keccak256Spec

Keccak256Spec
  keccak256
    produces correct results [✔]

Repl tests, Pact:

  • tests/pact/keccak256.repl
❯ cabal run test:hspec -- --match="pact tests"

PactTestsSpec
  pact tests
    ...
    tests/pact/keccak256.repl [✔]

Blocked on Pact 4.12 fork

❯ cabal run pact
pact> (keccak256[""])
"xdJGAYb3IzySfn2y3McDwOUAtlPKgic7e/rYBF2FpHA="
pact> (env-exec-config ['DisablePact412])
["DisablePact412"]
pact> (keccak256[""])
<interactive>:0:0:Error: Cannot resolve keccak256
pact>

Gas Benchmark

Gas Benchmark Code

import Test.Tasty.Bench

main :: IO ()
main = do
  defaultMain
    [ bgroup "Keccak256"
        [ benchHash 10
        , benchHash 100
        , benchHash 1_000
        , benchHash 10_000
        , benchHash 100_000
        , benchHash 1_000_000
        ]
    ]

benchHash :: Int -> Benchmark
benchHash n = env (evaluate (force (inputsN n))) $ \inputs ->
  bgroup ("Keccak256 - " ++ show n ++ " bytes")
    [ bench "single blob" $ nf (keccak256 . V.singleton) (input inputs)
    , bench "chunked" $ nf keccak256 (chunked inputs)
    ]

stringN :: Int -> Term Name
stringN n = TLiteral
  { _tLiteral = LString (Text.decodeUtf8 (Base64.encode (BS.replicate n 97)))
  , _tInfo = def
  }

data Inputs = Inputs
  { input :: !(Term Name)
  , chunked :: {-# unpack #-} !(Vector (Term Name))
  }
  deriving stock (Generic)
  deriving anyclass (NFData)

inputsN :: Int -> Inputs
inputsN n =
  let
    !string = stringN n
    !chunk = stringN (n `div` chunkSize)
  in
  Inputs
    { input = string
    , chunked = V.replicate chunkSize chunk
    }

chunkSize :: Int
chunkSize = 10

Gas Benchmark Results

All
  Keccak256
    Keccak256 - 10 bytes
      single blob: OK
        2.33 μs ± 223 ns
      chunked:     OK
        4.91 μs ± 208 ns
    Keccak256 - 100 bytes
      single blob: OK
        2.36 μs ± 173 ns
      chunked:     OK
        5.18 μs ± 188 ns
    Keccak256 - 1000 bytes
      single blob: OK
        5.70 μs ± 189 ns
      chunked:     OK
        8.67 μs ± 359 ns
    Keccak256 - 10000 bytes
      single blob: OK
        37.8 μs ± 3.3 μs
      chunked:     OK
        42.4 μs ± 3.6 μs
    Keccak256 - 100000 bytes
      single blob: OK
        369  μs ±  20 μs
      chunked:     OK
        367  μs ±  25 μs
    Keccak256 - 1000000 bytes
      single blob: OK
        3.66 ms ± 242 μs
      chunked:     OK
        3.62 ms ± 357 μs

Findings

Linear regression of single-blob points only:
single_blob

Linear regression of chunked points only:
chunks

From the single blob regression, the cost of keccak256 could be computed (in milligas) roughly as:

852.4 + 1.4632 * num_bytes(input)

From the chunked regression, the cost of keccak256 could be computed (in milligas) roughly as:

2119.6 + 1.446 * num_bytes(input)

This seems to suggest that for smaller inputs, the number of chunks matters more. This corroborates the story the benchmarks tell, as you can see that the difference between chunked and non-chunked input gets smaller as the total number of bytes grows larger. In practice, this convergence takes so long that we should charge per chunk.

I propose that the cost be this, per chunk:

2119.6 + 1.4632 * num_bytes(chunk)

To avoid rounding errors due to the small cost per bytes, we can cost this per 100 bytes:

2120 + 146 * (num_bytes(chunk) `div` 100)

With ceiling to avoid chunks of 99 bytes going ungassed:

2120 + 146 * ceiling (num_bytes(chunk) / 100)

And because defaultGasTable contains the base cost for a single call to keccak256, I believe the entry should be 1 gas, because any call does some work (even keccak256 []), but the rest is gassed per chunk.

@chessai chessai marked this pull request as draft March 19, 2024 23:26
@@ -461,7 +461,7 @@ Return ID if called during current pact execution, failing if not.
Obtain current pact build version.
```lisp
pact> (pact-version)
"4.10"
"4.11"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think the docs have been regenerated in a while - there are some changes that are not related to what I have touched.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general: The readthedocs website is deprecated, and we need to manually PR changes for docs.kadena.io, is this right @sstraatemans ?

@chessai chessai marked this pull request as ready for review March 19, 2024 23:50
@@ -461,7 +461,7 @@ Return ID if called during current pact execution, failing if not.
Obtain current pact build version.
```lisp
pact> (pact-version)
"4.10"
"4.11"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general: The readthedocs website is deprecated, and we need to manually PR changes for docs.kadena.io, is this right @sstraatemans ?

Copy link
Member

@jmcardon jmcardon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot be merged until we remove the unsafe lens access

@chessai
Copy link
Contributor Author

chessai commented Mar 20, 2024

Cannot be merged until we remove the unsafe lens access

I thought the lens access was safe because I was under the impression that natives were typechecked, but I was wrong

@chessai chessai force-pushed the chessai/add-keccak256-native branch 2 times, most recently from b71af95 to 332702e Compare March 20, 2024 21:53
Copy link
Member

@rsoeldner rsoeldner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chessai, I've updated the FV translation but what I've noticed is, that the output has changed.
I.e., in the initial version 332702e, the output of (keccak256 []) was: xdJGAYb3IzySfn2y3McDwOUAtlPKgic7e/rYBF2FpHA= (see

let h = "xdJGAYb3IzySfn2y3McDwOUAtlPKgic7e/rYBF2FpHA="
which succeeded), with the recent changes, its xdJGAYb3IzySfn2y3McDwOUAtlPKgic7e_rYBF2FpHA

I've updated the FV branch with the recent changes, but we should look at the correct behaviour.

@chessai chessai force-pushed the chessai/add-keccak256-native branch 2 times, most recently from 9c09175 to 4264ec4 Compare March 25, 2024 18:31
@chessai chessai force-pushed the chessai/add-keccak256-native branch 2 times, most recently from 329c428 to b78f703 Compare March 25, 2024 19:00
@chessai chessai force-pushed the chessai/add-keccak256-native branch from b78f703 to 4db0ad1 Compare March 25, 2024 19:39
Co-authored-by: rsoeldner <r.soeldner@gmail.com>
@chessai chessai force-pushed the chessai/add-keccak256-native branch from 4db0ad1 to 9bf3f05 Compare March 25, 2024 19:49
@rsoeldner rsoeldner merged commit 6f466ea into master Mar 27, 2024
9 checks passed
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

Successfully merging this pull request may close these issues.

6 participants