Skip to content
This repository has been archived by the owner on Oct 11, 2024. It is now read-only.

Directly compute packed length of RPC call #846

Merged
merged 6 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 11 additions & 34 deletions zeroex/ordervalidator/order_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@ import (
"math/big"
"net/http"
"regexp"
"strings"
"sync"
"time"

"github.com/0xProject/0x-mesh/constants"
"github.com/0xProject/0x-mesh/ethereum"
"github.com/0xProject/0x-mesh/ethereum/wrappers"
"github.com/0xProject/0x-mesh/zeroex"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/jpillora/backoff"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -233,7 +230,6 @@ type ValidationResults struct {
// OrderValidator validates 0x orders
type OrderValidator struct {
maxRequestContentLength int
devUtilsABI abi.ABI
devUtils *wrappers.DevUtilsCaller
coordinatorRegistry *wrappers.CoordinatorRegistryCaller
assetDataDecoder *zeroex.AssetDataDecoder
Expand All @@ -244,10 +240,6 @@ type OrderValidator struct {

// New instantiates a new order validator
func New(contractCaller bind.ContractCaller, chainID int, maxRequestContentLength int, contractAddresses ethereum.ContractAddresses) (*OrderValidator, error) {
devUtilsABI, err := abi.JSON(strings.NewReader(wrappers.DevUtilsABI))
if err != nil {
return nil, err
}
devUtils, err := wrappers.NewDevUtilsCaller(contractAddresses.DevUtils, contractCaller)
if err != nil {
return nil, err
Expand All @@ -260,7 +252,6 @@ func New(contractCaller bind.ContractCaller, chainID int, maxRequestContentLengt

return &OrderValidator{
maxRequestContentLength: maxRequestContentLength,
devUtilsABI: devUtilsABI,
devUtils: devUtils,
coordinatorRegistry: coordinatorRegistry,
assetDataDecoder: assetDataDecoder,
Expand Down Expand Up @@ -811,13 +802,6 @@ func (o *OrderValidator) isSupportedStaticCallData(staticCallAssetData zeroex.St
return true
}

// emptyGetOrderRelevantStatesCallDataByteLength is all the boilerplate ABI encoding required when calling
// `getOrderRelevantStates` that does not include the encoded SignedOrder. By subtracting this amount from the
// calldata length returned from encoding a call to `getOrderRelevantStates` involving a single SignedOrder, we
// get the number of bytes taken up by the SignedOrder alone.
// i.e.: len(`"0x7f46448d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`)
const emptyGetOrderRelevantStatesCallDataByteLength = 268

// jsonRPCPayloadByteLength is the number of bytes occupied by the default call to `getOrderRelevantStates` with 0 signedOrders
// passed in. The `data` includes the empty `getOrderRelevantStates` calldata.
/*
Expand All @@ -837,23 +821,16 @@ const emptyGetOrderRelevantStatesCallDataByteLength = 268
*/
const jsonRPCPayloadByteLength = 444

func (o *OrderValidator) computeABIEncodedSignedOrderByteLength(signedOrder *zeroex.SignedOrder) (int, error) {
trimmedOrder := signedOrder.Trim()
data, err := o.devUtilsABI.Pack(
"getOrderRelevantStates",
[]wrappers.LibOrderOrder{trimmedOrder},
[][]byte{signedOrder.Signature},
)
if err != nil {
return 0, err
}
dataBytes := hexutil.Bytes(data)
encodedData, err := json.Marshal(dataBytes)
if err != nil {
return 0, err
}
encodedSignedOrderByteLength := len(encodedData) - emptyGetOrderRelevantStatesCallDataByteLength
return encodedSignedOrderByteLength, nil
func numWords(bytes []byte) int {
return (len(bytes) + 31) / 32
}

func computeABIEncodedSignedOrderStringLength(signedOrder *zeroex.SignedOrder) int {
// The fixed size fields in a SignedOrder take up 1536 bytes. The variable length fields take up 64 characters per 256-bit word. This is because each byte in signedOrder is as two bytes in the JSON string.
return 1536 + 64*(numWords(signedOrder.Order.MakerAssetData)+
numWords(signedOrder.Order.TakerAssetData)+
numWords(signedOrder.Order.MakerFeeAssetData)+
numWords(signedOrder.Order.MakerFeeAssetData))
}

// computeOptimalChunkSizes splits the signedOrders into chunks where the payload size of each chunk
Expand All @@ -866,7 +843,7 @@ func (o *OrderValidator) computeOptimalChunkSizes(signedOrders []*zeroex.SignedO
payloadLength := jsonRPCPayloadByteLength
nextChunkSize := 0
for _, signedOrder := range signedOrders {
encodedSignedOrderByteLength, _ := o.computeABIEncodedSignedOrderByteLength(signedOrder)
encodedSignedOrderByteLength := computeABIEncodedSignedOrderStringLength(signedOrder)
if payloadLength+encodedSignedOrderByteLength < o.maxRequestContentLength {
payloadLength += encodedSignedOrderByteLength
nextChunkSize++
Expand Down
97 changes: 97 additions & 0 deletions zeroex/ordervalidator/order_validator_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// +build js, wasm

package ordervalidator

import (
"math/big"
"testing"
"time"

"github.com/0xProject/0x-mesh/constants"
"github.com/0xProject/0x-mesh/ethereum"
"github.com/0xProject/0x-mesh/ethereum/ethrpcclient"
"github.com/0xProject/0x-mesh/ethereum/ratelimit"
"github.com/0xProject/0x-mesh/zeroex"
"github.com/ethereum/go-ethereum/common"
ethrpc "github.com/ethereum/go-ethereum/rpc"
)

const (
defaultEthRPCTimeout = 5 * time.Second
)

var (
signedErc20Orders []*zeroex.SignedOrder
signedMultiAssetOrders []*zeroex.SignedOrder
orderValidator *OrderValidator
)

func init() {
rpcClient, err := ethrpc.Dial(constants.GanacheEndpoint)
if err != nil {
panic(err)
}
ethRPCClient, err := ethrpcclient.New(rpcClient, defaultEthRPCTimeout, ratelimit.NewUnlimited())
if err != nil {
panic(err)
}
orderValidator, err = New(ethRPCClient, constants.TestChainID, constants.TestMaxContentLength, ethereum.GanacheAddresses)
if err != nil {
panic(err)
}

erc20Data := common.Hex2Bytes("f47261b000000000000000000000000038ae374ecf4db50b0ff37125b591a04997106a32")
erc20Order := zeroex.Order{
ChainID: big.NewInt(constants.TestChainID),
MakerAddress: constants.GanacheAccount1,
TakerAddress: constants.NullAddress,
SenderAddress: constants.NullAddress,
FeeRecipientAddress: constants.NullAddress,
MakerAssetData: erc20Data,
MakerFeeAssetData: constants.NullBytes,
TakerAssetData: erc20Data,
TakerFeeAssetData: constants.NullBytes,
Salt: big.NewInt(int64(time.Now().Nanosecond())),
MakerFee: big.NewInt(0),
TakerFee: big.NewInt(0),
MakerAssetAmount: big.NewInt(100),
TakerAssetAmount: big.NewInt(42),
ExpirationTimeSeconds: big.NewInt(time.Now().Add(24 * time.Hour).Unix()),
ExchangeAddress: ethereum.GanacheAddresses.Exchange,
}
signedErc20Order, err := zeroex.SignTestOrder(&erc20Order)
if err != nil {
panic(err)
}
for i := 1; i < 100; i++ {
signedErc20Orders = append(signedErc20Orders, signedErc20Order)
}

multiAssetData := common.Hex2Bytes("94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000204a7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")

multiAssetOrder := erc20Order
multiAssetOrder.MakerAssetData = multiAssetData
multiAssetOrder.MakerFeeAssetData = multiAssetData
multiAssetOrder.TakerAssetData = multiAssetData
multiAssetOrder.TakerFeeAssetData = multiAssetData

signedMultiAssetOrder, err := zeroex.SignTestOrder(&multiAssetOrder)
if err != nil {
panic(err)
}
for i := 1; i < 100; i++ {
signedMultiAssetOrders = append(signedMultiAssetOrders, signedMultiAssetOrder)
}
}

func BenchmarkErc20ComputeOptimalChunkSizes(b *testing.B) {
for n := 0; n < b.N; n++ {
orderValidator.computeOptimalChunkSizes(signedErc20Orders)
}
}

func BenchmarkMultiAssetComputeOptimalChunkSizes(b *testing.B) {
for n := 0; n < b.N; n++ {
orderValidator.computeOptimalChunkSizes(signedMultiAssetOrders)
}
}
61 changes: 61 additions & 0 deletions zeroex/ordervalidator/order_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ package ordervalidator

import (
"context"
"encoding/json"
"flag"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

Expand All @@ -23,8 +25,10 @@ import (
"github.com/0xProject/0x-mesh/scenario"
"github.com/0xProject/0x-mesh/scenario/orderopts"
"github.com/0xProject/0x-mesh/zeroex"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
Expand All @@ -35,6 +39,13 @@ import (

const areNewOrders = false

// emptyGetOrderRelevantStatesCallDataByteLength is all the boilerplate ABI encoding required when calling
// `getOrderRelevantStates` that does not include the encoded SignedOrder. By subtracting this amount from the
// calldata length returned from encoding a call to `getOrderRelevantStates` involving a single SignedOrder, we
// get the number of bytes taken up by the SignedOrder alone.
// i.e.: len(`"0x7f46448d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`)
const emptyGetOrderRelevantStatesCallDataStringLength = 268

const (
maxEthRPCRequestsPer24HrUTC = 1000000
maxEthRPCRequestsPerSeconds = 1000.0
Expand Down Expand Up @@ -435,6 +446,56 @@ func TestComputeOptimalChunkSizesMultiAssetOrder(t *testing.T) {
assert.Equal(t, expectedChunkSizes, chunkSizes)
}

func abiEncode(signedOrder *zeroex.SignedOrder) ([]byte, error) {
trimmedOrder := signedOrder.Trim()

devUtilsABI, err := abi.JSON(strings.NewReader(wrappers.DevUtilsABI))
if err != nil {
return []byte{}, err
}

data, err := devUtilsABI.Pack(
"getOrderRelevantStates",
[]wrappers.LibOrderOrder{trimmedOrder},
[][]byte{signedOrder.Signature},
)
if err != nil {
return []byte{}, err
}

dataBytes := hexutil.Bytes(data)
encodedData, err := json.Marshal(dataBytes)
if err != nil {
return []byte{}, err
}

return encodedData, nil
}

func TestComputeABIEncodedSignedOrderStringByteLength(t *testing.T) {
testOrder := scenario.NewSignedTestOrder(t)

testMultiAssetOrder := scenario.NewSignedTestOrder(t)
testMultiAssetOrder.Order.MakerAssetData = common.Hex2Bytes("123412304102340120350120340123041023401234102341234234523452345234")
testMultiAssetOrder.Order.MakerAssetData = common.Hex2Bytes("132519348523094582039457283452")
testMultiAssetOrder.Order.MakerAssetData = multiAssetAssetData
testMultiAssetOrder.Order.MakerAssetData = common.Hex2Bytes("324857203942034562893723452345246529837")

testCases := []*zeroex.SignedOrder{testOrder, testMultiAssetOrder}

for _, signedOrder := range testCases {
label := fmt.Sprintf("test order: %v", signedOrder)

encoded, err := abiEncode(signedOrder)
require.NoError(t, err)
expectedLength := len(encoded) - emptyGetOrderRelevantStatesCallDataStringLength

length := computeABIEncodedSignedOrderStringLength(signedOrder)

assert.Equal(t, expectedLength, length, label)
}
}

func setupSubTest(t *testing.T) func(t *testing.T) {
blockchainLifecycle.Start(t)
return func(t *testing.T) {
Expand Down