From 21d245c7dbee19b70fbaa82eab2442692879ec8a Mon Sep 17 00:00:00 2001 From: BananaLF <864685021@qq.com> Date: Tue, 11 Jul 2023 09:42:53 +0800 Subject: [PATCH 1/9] fix erc20 to cw20 recipient can not user 0x prefix address (#3197) --- dev/vmbridge.sh | 2 +- dev/wasm/erc20/src/contract.rs | 28 ------------------------- dev/wasm/erc20/src/msg.rs | 20 ------------------ dev/wasm/vmbridge-erc20/src/contract.rs | 10 ++++----- x/vmbridge/keeper/evm_test.go | 14 ++++++------- x/vmbridge/keeper/keeper_test.go | 5 ++--- x/vmbridge/keeper/wasm.go | 8 ++----- x/vmbridge/keeper/wasm_test.go | 24 ++++++++++----------- 8 files changed, 29 insertions(+), 82 deletions(-) diff --git a/dev/vmbridge.sh b/dev/vmbridge.sh index 8b3abd2a97..2bd3c753d9 100755 --- a/dev/vmbridge.sh +++ b/dev/vmbridge.sh @@ -1,4 +1,4 @@ -res=$(exchaincli tx wasm store ./wasm/vmbridge-erc20/artifacts/cw_erc20.wasm --fees 0.01okt --from captain --gas=20000000 -b block -y) +res=$(exchaincli tx wasm store ./wasm/erc20/artifacts/cw_erc20.wasm --fees 0.01okt --from captain --gas=20000000 -b block -y) echo "store--------------" echo $res code_id=$(echo "$res" | jq '.logs[0].events[1].attributes[0].value' | sed 's/\"//g') diff --git a/dev/wasm/erc20/src/contract.rs b/dev/wasm/erc20/src/contract.rs index 772ccdac8e..34cca06b54 100644 --- a/dev/wasm/erc20/src/contract.rs +++ b/dev/wasm/erc20/src/contract.rs @@ -86,12 +86,6 @@ pub fn execute( recipient, amount, } => try_send_to_erc20(deps, env,evmContract,recipient,amount,info), - - ExecuteMsg::CallToEvmMsg { - evmaddr, - calldata, - value, - } => try_call_to_evm(deps, env,evmaddr,calldata,value,info), } } @@ -160,29 +154,7 @@ fn try_mint_cw20( .add_attribute("amount", amount.to_string())) } -fn try_call_to_evm( - deps: DepsMut, - _env: Env, - evmaddr: String, - calldata: String, - value: Uint128, - info: MessageInfo, -) -> Result, ContractError> { - let submsg = SendToEvmMsg { - sender: _env.contract.address.to_string(), - evmaddr: evmaddr.to_string(), - calldata: calldata, - value: value, - }; - - Ok(Response::new() - .add_attribute("action", "call to evm") - .add_attribute("evmaddr", evmaddr.to_string()) - .add_attribute("value", value.to_string()) - .add_message(submsg) - .set_data(b"the result data")) -} fn try_send_to_erc20( deps: DepsMut, diff --git a/dev/wasm/erc20/src/msg.rs b/dev/wasm/erc20/src/msg.rs index abcbdd2085..1d85691378 100644 --- a/dev/wasm/erc20/src/msg.rs +++ b/dev/wasm/erc20/src/msg.rs @@ -48,11 +48,6 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, }, - CallToEvm { - evmContract: String, - calldata: String, - value: Uint128, - } } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] @@ -71,21 +66,6 @@ impl Into> for SendToEvmMsg { } impl CustomMsg for SendToEvmMsg {} -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub struct CallToEvmMsg { - pub sender: String, - pub evmaddr: String, - pub calldata: String, - pub value: Uint128, - -} -impl Into> for CallToEvmMsg { - fn into(self) -> CosmosMsg { - CosmosMsg::Custom(self) - } -} -impl CustomMsg for CallToEvmMsg {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/dev/wasm/vmbridge-erc20/src/contract.rs b/dev/wasm/vmbridge-erc20/src/contract.rs index 475cf5af98..500c4bf507 100644 --- a/dev/wasm/vmbridge-erc20/src/contract.rs +++ b/dev/wasm/vmbridge-erc20/src/contract.rs @@ -157,11 +157,11 @@ fn try_mint_cw20( recipient: String, amount: Uint128, ) -> Result, ContractError> { - if info.sender.to_string() != EVM_CONTRACT_ADDR.to_string() { - return Err(ContractError::ContractERC20Err { - addr:info.sender.to_string() - }); - } + // if info.sender.to_string() != EVM_CONTRACT_ADDR.to_string() { + // return Err(ContractError::ContractERC20Err { + // addr:info.sender.to_string() + // }); + // } let amount_raw = amount.u128(); let recipient_address = deps.api.addr_validate(recipient.as_str())?; let mut account_balance = read_balance(deps.storage, &recipient_address)?; diff --git a/x/vmbridge/keeper/evm_test.go b/x/vmbridge/keeper/evm_test.go index 174d822980..a843fcebed 100644 --- a/x/vmbridge/keeper/evm_test.go +++ b/x/vmbridge/keeper/evm_test.go @@ -216,25 +216,25 @@ func (suite *KeeperTestSuite) TestSendToWasmEventHandler_Handle() { data = input }, func() { - queryAddr := sdk.AccAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + queryAddr := sdk.WasmAddress(ethAddr.Bytes()) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, - types.ErrIsNotOKCAddr, + nil, }, { "normal topic,recipient is ex", func() { wasmAddrStr := suite.wasmContract.String() - queryAddr := sdk.AccAddress(ethAddr.Bytes()) + queryAddr := sdk.WasmAddress(ethAddr.Bytes()) input, err := getSendToWasmEventData(wasmAddrStr, queryAddr.String(), big.NewInt(1)) suite.Require().NoError(err) data = input }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, @@ -244,14 +244,14 @@ func (suite *KeeperTestSuite) TestSendToWasmEventHandler_Handle() { "normal topic,amount is zero", func() { wasmAddrStr := suite.wasmContract.String() - queryAddr := sdk.AccAddress(ethAddr.Bytes()) + queryAddr := sdk.WasmAddress(ethAddr.Bytes()) input, err := getSendToWasmEventData(wasmAddrStr, queryAddr.String(), big.NewInt(0)) suite.Require().NoError(err) data = input }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"0\"}", string(result)) }, diff --git a/x/vmbridge/keeper/keeper_test.go b/x/vmbridge/keeper/keeper_test.go index 5bae132cfd..a8c6f021c0 100644 --- a/x/vmbridge/keeper/keeper_test.go +++ b/x/vmbridge/keeper/keeper_test.go @@ -37,7 +37,7 @@ type KeeperTestSuite struct { keeper *keeper.Keeper addr sdk.AccAddress - wasmContract sdk.AccAddress + wasmContract sdk.WasmAddress codeId uint64 evmContract common.Address @@ -87,8 +87,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.Require().NoError(err) initMsg := []byte(fmt.Sprintf("{\"decimals\":10,\"initial_balances\":[{\"address\":\"%s\",\"amount\":\"100000000\"}],\"name\":\"my test token\", \"symbol\":\"MTT\"}", suite.addr.String())) - temp, _, err := suite.app.WasmPermissionKeeper.Instantiate(suite.ctx, suite.codeId, sdk.AccToAWasmddress(suite.addr), sdk.AccToAWasmddress(suite.addr), initMsg, "label", sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}) - suite.wasmContract = sdk.WasmToAccAddress(temp) + suite.wasmContract, _, err = suite.app.WasmPermissionKeeper.Instantiate(suite.ctx, suite.codeId, sdk.AccToAWasmddress(suite.addr), sdk.AccToAWasmddress(suite.addr), initMsg, "label", sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}) suite.Require().NoError(err) palyload := "60806040526040518060600160405280603d815260200162002355603d913960079080519060200190620000359291906200004a565b503480156200004357600080fd5b506200015f565b8280546200005890620000fa565b90600052602060002090601f0160209004810192826200007c5760008555620000c8565b82601f106200009757805160ff1916838001178555620000c8565b82800160010185558215620000c8579182015b82811115620000c7578251825591602001919060010190620000aa565b5b509050620000d79190620000db565b5090565b5b80821115620000f6576000816000905550600101620000dc565b5090565b600060028204905060018216806200011357607f821691505b602082108114156200012a576200012962000130565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6121e6806200016f6000396000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c806370a08231116100ad578063cc1207c011610071578063cc1207c014610330578063d069cf761461034c578063d241877c1461037c578063dd62ed3e1461039a578063ee366654146103ca57610121565b806370a08231146102665780638e155cee1461029657806395d89b41146102b2578063a457c2d7146102d0578063a9059cbb1461030057610121565b8063313ce567116100f4578063313ce567146101c257806335b2bd2d146101e057806339509351146101fe5780633a0c76ea1461022e57806340c10f191461024a57610121565b806306fdde0314610126578063095ea7b31461014457806318160ddd1461017457806323b872dd14610192575b600080fd5b61012e6103e8565b60405161013b91906119a9565b60405180910390f35b61015e600480360381019061015991906114c5565b61047a565b60405161016b919061198e565b60405180910390f35b61017c610496565b6040516101899190611b70565b60405180910390f35b6101ac60048036038101906101a79190611472565b6104a0565b6040516101b9919061198e565b60405180910390f35b6101ca6104c8565b6040516101d79190611b8b565b60405180910390f35b6101e86104df565b6040516101f59190611973565b60405180910390f35b610218600480360381019061021391906114c5565b6104f7565b604051610225919061198e565b60405180910390f35b610248600480360381019061024391906115c2565b61059a565b005b610264600480360381019061025f91906114c5565b6105e4565b005b610280600480360381019061027b9190611405565b6105f2565b60405161028d9190611b70565b60405180910390f35b6102b060048036038101906102ab9190611579565b61063b565b005b6102ba610655565b6040516102c791906119a9565b60405180910390f35b6102ea60048036038101906102e591906114c5565b6106e7565b6040516102f7919061198e565b60405180910390f35b61031a600480360381019061031591906114c5565b6107ca565b604051610327919061198e565b60405180910390f35b61034a6004803603810190610345919061164d565b6107e6565b005b61036660048036038101906103619190611505565b6107f5565b604051610373919061198e565b60405180910390f35b6103846108b4565b60405161039191906119a9565b60405180910390f35b6103b460048036038101906103af9190611432565b610942565b6040516103c19190611b70565b60405180910390f35b6103d26109c9565b6040516103df91906119a9565b60405180910390f35b6060600180546103f790611d59565b80601f016020809104026020016040519081016040528092919081815260200182805461042390611d59565b80156104705780601f1061044557610100808354040283529160200191610470565b820191906000526020600020905b81548152906001019060200180831161045357829003601f168201915b5050505050905090565b60008033905061048b8185856109d8565b600191505092915050565b6000600454905090565b6000803390506104b1858285610ba3565b6104bc858585610c2f565b60019150509392505050565b6000600360009054906101000a900460ff16905090565b73c63cf6c8e1f3df41085e9d8af49584dae1432b4f81565b60008033905061058f818585600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461058a9190611c38565b6109d8565b600191505092915050565b6105a43382610e9d565b7f41e4c36823b869e11ae85a7e623a332d31d961ba9ed670a3c9cb71c973c53caa8284836040516105d7939291906119cb565b60405180910390a1505050565b6105ee828261105e565b5050565b6000600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b806007908051906020019061065192919061125d565b5050565b60606002805461066490611d59565b80601f016020809104026020016040519081016040528092919081815260200182805461069090611d59565b80156106dd5780601f106106b2576101008083540402835291602001916106dd565b820191906000526020600020905b8154815290600101906020018083116106c057829003601f168201915b5050505050905090565b6000803390506000600660008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050838110156107b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107a890611b30565b60405180910390fd5b6107be82868684036109d8565b60019250505092915050565b6000803390506107db818585610c2f565b600191505092915050565b6107f18283836111a7565b5050565b600073c63cf6c8e1f3df41085e9d8af49584dae1432b4f73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461084357600080fd5b6007604051602001610855919061195c565b60405160208183030381529060405280519060200120858560405160200161087e929190611943565b604051602081830303815290604052805190602001201461089e57600080fd5b6108a8838361105e565b60019050949350505050565b600780546108c190611d59565b80601f01602080910402602001604051908101604052809291908181526020018280546108ed90611d59565b801561093a5780601f1061090f5761010080835404028352916020019161093a565b820191906000526020600020905b81548152906001019060200180831161091d57829003601f168201915b505050505081565b6000600660008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60606109d3610655565b905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610a48576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a3f90611b10565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610ab8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610aaf90611a50565b60405180910390fd5b80600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92583604051610b969190611b70565b60405180910390a3505050565b6000610baf8484610942565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610c295781811015610c1b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c1290611a90565b60405180910390fd5b610c2884848484036109d8565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610c9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c9690611af0565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d0f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d0690611a10565b60405180910390fd5b6000600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610d96576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d8d90611ab0565b60405180910390fd5b818103600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610e2b9190611c38565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610e8f9190611b70565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f0d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f0490611ad0565b60405180910390fd5b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610f94576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f8b90611a30565b60405180910390fd5b818103600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508160046000828254610fec9190611c8e565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110519190611b70565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156110ce576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c590611b50565b60405180910390fd5b80600460008282546110e09190611c38565b9250508190555080600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546111369190611c38565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161119b9190611b70565b60405180910390a35050565b60008054906101000a900460ff16156111f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111ec90611a70565b60405180910390fd5b60016000806101000a81548160ff021916908315150217905550826001908051906020019061122592919061125d565b50816002908051906020019061123c92919061125d565b5080600360006101000a81548160ff021916908360ff160217905550505050565b82805461126990611d59565b90600052602060002090601f01602090048101928261128b57600085556112d2565b82601f106112a457805160ff19168380011785556112d2565b828001600101855582156112d2579182015b828111156112d15782518255916020019190600101906112b6565b5b5090506112df91906112e3565b5090565b5b808211156112fc5760008160009055506001016112e4565b5090565b600061131361130e84611bcb565b611ba6565b90508281526020810184848401111561132f5761132e611e58565b5b61133a848285611d17565b509392505050565b6000813590506113518161216b565b92915050565b60008083601f84011261136d5761136c611e4e565b5b8235905067ffffffffffffffff81111561138a57611389611e49565b5b6020830191508360018202830111156113a6576113a5611e53565b5b9250929050565b600082601f8301126113c2576113c1611e4e565b5b81356113d2848260208601611300565b91505092915050565b6000813590506113ea81612182565b92915050565b6000813590506113ff81612199565b92915050565b60006020828403121561141b5761141a611e62565b5b600061142984828501611342565b91505092915050565b6000806040838503121561144957611448611e62565b5b600061145785828601611342565b925050602061146885828601611342565b9150509250929050565b60008060006060848603121561148b5761148a611e62565b5b600061149986828701611342565b93505060206114aa86828701611342565b92505060406114bb868287016113db565b9150509250925092565b600080604083850312156114dc576114db611e62565b5b60006114ea85828601611342565b92505060206114fb858286016113db565b9150509250929050565b6000806000806060858703121561151f5761151e611e62565b5b600085013567ffffffffffffffff81111561153d5761153c611e5d565b5b61154987828801611357565b9450945050602061155c87828801611342565b925050604061156d878288016113db565b91505092959194509250565b60006020828403121561158f5761158e611e62565b5b600082013567ffffffffffffffff8111156115ad576115ac611e5d565b5b6115b9848285016113ad565b91505092915050565b6000806000606084860312156115db576115da611e62565b5b600084013567ffffffffffffffff8111156115f9576115f8611e5d565b5b611605868287016113ad565b935050602084013567ffffffffffffffff81111561162657611625611e5d565b5b611632868287016113ad565b9250506040611643868287016113db565b9150509250925092565b6000806040838503121561166457611663611e62565b5b600083013567ffffffffffffffff81111561168257611681611e5d565b5b61168e858286016113ad565b925050602061169f858286016113f0565b9150509250929050565b6116b281611cc2565b82525050565b6116c181611cd4565b82525050565b60006116d38385611c2d565b93506116e0838584611d17565b82840190509392505050565b60006116f782611c11565b6117018185611c1c565b9350611711818560208601611d26565b61171a81611e67565b840191505092915050565b6000815461173281611d59565b61173c8186611c2d565b9450600182166000811461175757600181146117685761179b565b60ff1983168652818601935061179b565b61177185611bfc565b60005b8381101561179357815481890152600182019150602081019050611774565b838801955050505b50505092915050565b60006117b1602383611c1c565b91506117bc82611e78565b604082019050919050565b60006117d4602283611c1c565b91506117df82611ec7565b604082019050919050565b60006117f7602283611c1c565b915061180282611f16565b604082019050919050565b600061181a601b83611c1c565b915061182582611f65565b602082019050919050565b600061183d601d83611c1c565b915061184882611f8e565b602082019050919050565b6000611860602683611c1c565b915061186b82611fb7565b604082019050919050565b6000611883602183611c1c565b915061188e82612006565b604082019050919050565b60006118a6602583611c1c565b91506118b182612055565b604082019050919050565b60006118c9602483611c1c565b91506118d4826120a4565b604082019050919050565b60006118ec602583611c1c565b91506118f7826120f3565b604082019050919050565b600061190f601f83611c1c565b915061191a82612142565b602082019050919050565b61192e81611d00565b82525050565b61193d81611d0a565b82525050565b60006119508284866116c7565b91508190509392505050565b60006119688284611725565b915081905092915050565b600060208201905061198860008301846116a9565b92915050565b60006020820190506119a360008301846116b8565b92915050565b600060208201905081810360008301526119c381846116ec565b905092915050565b600060608201905081810360008301526119e581866116ec565b905081810360208301526119f981856116ec565b9050611a086040830184611925565b949350505050565b60006020820190508181036000830152611a29816117a4565b9050919050565b60006020820190508181036000830152611a49816117c7565b9050919050565b60006020820190508181036000830152611a69816117ea565b9050919050565b60006020820190508181036000830152611a898161180d565b9050919050565b60006020820190508181036000830152611aa981611830565b9050919050565b60006020820190508181036000830152611ac981611853565b9050919050565b60006020820190508181036000830152611ae981611876565b9050919050565b60006020820190508181036000830152611b0981611899565b9050919050565b60006020820190508181036000830152611b29816118bc565b9050919050565b60006020820190508181036000830152611b49816118df565b9050919050565b60006020820190508181036000830152611b6981611902565b9050919050565b6000602082019050611b856000830184611925565b92915050565b6000602082019050611ba06000830184611934565b92915050565b6000611bb0611bc1565b9050611bbc8282611d8b565b919050565b6000604051905090565b600067ffffffffffffffff821115611be657611be5611e1a565b5b611bef82611e67565b9050602081019050919050565b60008190508160005260206000209050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b6000611c4382611d00565b9150611c4e83611d00565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611c8357611c82611dbc565b5b828201905092915050565b6000611c9982611d00565b9150611ca483611d00565b925082821015611cb757611cb6611dbc565b5b828203905092915050565b6000611ccd82611ce0565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b83811015611d44578082015181840152602081019050611d29565b83811115611d53576000848401525b50505050565b60006002820490506001821680611d7157607f821691505b60208210811415611d8557611d84611deb565b5b50919050565b611d9482611e67565b810181811067ffffffffffffffff82111715611db357611db2611e1a565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60008201527f6365000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20616c726561647920696e697469616c697a65643b0000000000600082015250565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b61217481611cc2565b811461217f57600080fd5b50565b61218b81611d00565b811461219657600080fd5b50565b6121a281611d0a565b81146121ad57600080fd5b5056fea264697066735822122022151d41dd9654958225e494d6adc336a64e7ff8fe7993ba1764f69906eb984064736f6c6343000807003365783134686a32746176713866706573647778786375343472747933686839307668756a7276636d73746c347a723374786d6676773973366671753237" diff --git a/x/vmbridge/keeper/wasm.go b/x/vmbridge/keeper/wasm.go index bec97e7659..b64cd56ba2 100644 --- a/x/vmbridge/keeper/wasm.go +++ b/x/vmbridge/keeper/wasm.go @@ -14,11 +14,7 @@ import ( ) func (k Keeper) SendToWasm(ctx sdk.Context, caller sdk.AccAddress, wasmContractAddr, recipient string, amount sdk.Int) error { - // must check recipient is ex address - if !sdk.IsOKCAddress(recipient) { - return types.ErrIsNotOKCAddr - } - to, err := sdk.AccAddressFromBech32(recipient) + _, err := sdk.WasmAddressFromBech32(recipient) if err != nil { return err } @@ -26,7 +22,7 @@ func (k Keeper) SendToWasm(ctx sdk.Context, caller sdk.AccAddress, wasmContractA if amount.IsNegative() { return types.ErrAmountNegative } - input, err := types.GetMintCW20Input(amount.String(), sdk.AccToAWasmddress(to).String()) + input, err := types.GetMintCW20Input(amount.String(), recipient) if err != nil { return err } diff --git a/x/vmbridge/keeper/wasm_test.go b/x/vmbridge/keeper/wasm_test.go index 328a3e72be..7afe5ad4c5 100644 --- a/x/vmbridge/keeper/wasm_test.go +++ b/x/vmbridge/keeper/wasm_test.go @@ -27,7 +27,7 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { reset := func() { caller = sdk.AccAddress(contract.Bytes()) wasmContractAddr = suite.wasmContract.String() - recipient = sdk.AccAddress(ethAddr.Bytes()).String() + recipient = ethAddr.String() amount = sdk.NewInt(1) } testCases := []struct { @@ -42,7 +42,7 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, @@ -55,11 +55,11 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, - nil, + errors.New("execute wasm contract failed: Generic error: addr_validate errored: Address is not normalized"), }, { "recipient is 0x", @@ -68,11 +68,11 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, - types.ErrIsNotOKCAddr, + nil, }, { "recipient is wasmaddr", @@ -81,24 +81,24 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { }, func() { queryAddr := sdk.WasmAddress(make([]byte, 32)) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, - nil, + errors.New("execute wasm contract failed: Generic error: addr_validate errored: Address is not normalized"), }, { - "recipient is wasmaddr 0x", + "recipient is 0x 32", func() { recipient = "0x" + hex.EncodeToString(make([]byte, 32)) }, func() { queryAddr := sdk.WasmAddress(make([]byte, 32)) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"1\"}", string(result)) }, - types.ErrIsNotOKCAddr, + errors.New("incorrect address length"), }, { "normal topic,amount is zero", @@ -107,7 +107,7 @@ func (suite *KeeperTestSuite) TestKeeper_SendToWasm() { }, func() { queryAddr := sdk.WasmAddress(ethAddr.Bytes()) - result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, sdk.AccToAWasmddress(suite.wasmContract), []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) + result, err := suite.app.WasmKeeper.QuerySmart(suite.ctx, suite.wasmContract, []byte(fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", queryAddr.String()))) suite.Require().NoError(err) suite.Require().Equal("{\"balance\":\"0\"}", string(result)) }, From 9483c69ead10ed19712c394bcef4d6c80331e582 Mon Sep 17 00:00:00 2001 From: Leo <52782564+LeoGuo621@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:25:57 +0800 Subject: [PATCH 2/9] parallel execution optimization for AnteErr tx & fix e2c smb bug (#3188) * fix smb bug caused by e2c before venus6 * fix bug * optimize code * Update baseapp.go fix typo --------- Co-authored-by: KamiD <44460798+KamiD@users.noreply.github.com> --- app/app_parallel.go | 25 ++-- libs/cosmos-sdk/baseapp/baseapp.go | 8 +- libs/cosmos-sdk/baseapp/baseapp_parallel.go | 126 ++++++++++++-------- libs/cosmos-sdk/types/context.go | 11 +- libs/cosmos-sdk/types/utils.go | 2 +- x/wasm/keeper/ante.go | 14 +-- 6 files changed, 116 insertions(+), 70 deletions(-) diff --git a/app/app_parallel.go b/app/app_parallel.go index 34cd247fa4..eb3bba5337 100644 --- a/app/app_parallel.go +++ b/app/app_parallel.go @@ -117,15 +117,17 @@ func getTxFeeHandler() sdk.GetTxFeeHandler { // getTxFeeAndFromHandler get tx fee and from func getTxFeeAndFromHandler(ek appante.EVMKeeper) sdk.GetTxFeeAndFromHandler { - return func(ctx sdk.Context, tx sdk.Tx) (fee sdk.Coins, isEvm bool, isE2C bool, from string, to string, err error, supportPara bool) { + return func(ctx sdk.Context, tx sdk.Tx) (fee sdk.Coins, isEvm bool, needUpdateTXCounter bool, from string, to string, err error, supportPara bool) { if evmTx, ok := tx.(*evmtypes.MsgEthereumTx); ok { isEvm = true supportPara = true if appante.IsE2CTx(ek, &ctx, evmTx) { - isE2C = true + if tmtypes.HigherThanVenus6(ctx.BlockHeight()) { + needUpdateTXCounter = true + } // E2C will include cosmos Msg in the Payload. // Sometimes, this Msg do not support parallel execution. - if !isParaSupportedE2CMsg(evmTx.Data.Payload) { + if !tmtypes.HigherThanVenus6(ctx.BlockHeight()) || !isParaSupportedE2CMsg(evmTx.Data.Payload) { supportPara = false } } @@ -143,11 +145,18 @@ func getTxFeeAndFromHandler(ek appante.EVMKeeper) sdk.GetTxFeeAndFromHandler { } } else if feeTx, ok := tx.(authante.FeeTx); ok { fee = feeTx.GetFee() - if stdTx, ok := tx.(*auth.StdTx); ok && len(stdTx.Msgs) == 1 { // only support one message - if msg, ok := stdTx.Msgs[0].(interface{ CalFromAndToForPara() (string, string) }); ok { - from, to = msg.CalFromAndToForPara() - if tmtypes.HigherThanVenus6(ctx.BlockHeight()) { - supportPara = true + if tx.GetType() == sdk.StdTxType { + if tmtypes.HigherThanEarth(ctx.BlockHeight()) { + needUpdateTXCounter = true + } + txMsgs := tx.GetMsgs() + // only support one message + if len(txMsgs) == 1 { + if msg, ok := txMsgs[0].(interface{ CalFromAndToForPara() (string, string) }); ok { + from, to = msg.CalFromAndToForPara() + if tmtypes.HigherThanVenus6(ctx.BlockHeight()) { + supportPara = true + } } } } diff --git a/libs/cosmos-sdk/baseapp/baseapp.go b/libs/cosmos-sdk/baseapp/baseapp.go index 994cc1f679..4af0353f1a 100644 --- a/libs/cosmos-sdk/baseapp/baseapp.go +++ b/libs/cosmos-sdk/baseapp/baseapp.go @@ -697,10 +697,16 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context ctx.SetGasMeter(sdk.NewInfiniteGasMeter()) } if app.parallelTxManage.isAsyncDeliverTx && mode == runTxModeDeliverInAsync { + app.parallelTxManage.txByteMpCMIndexLock.RLock() ctx.SetParaMsg(&sdk.ParaMsg{ - HaveCosmosTxInBlock: app.parallelTxManage.haveCosmosTxInBlock, + // Concurrency security issues need to be considered here, + // and there is a small probability that NeedUpdateTXCounter() will be wrong + // due to concurrent reading and writing of pm.txIndexMpUpdateTXCounter (slice), + // but such tx will be rerun, so this case can be ignored. + NeedUpdateTXCounter: app.parallelTxManage.NeedUpdateTXCounter(), CosmosIndexInBlock: app.parallelTxManage.txByteMpCosmosIndex[string(txBytes)], }) + app.parallelTxManage.txByteMpCMIndexLock.RUnlock() ctx.SetTxBytes(txBytes) ctx.ResetWatcher() } diff --git a/libs/cosmos-sdk/baseapp/baseapp_parallel.go b/libs/cosmos-sdk/baseapp/baseapp_parallel.go index 05fb432253..81435b9820 100644 --- a/libs/cosmos-sdk/baseapp/baseapp_parallel.go +++ b/libs/cosmos-sdk/baseapp/baseapp_parallel.go @@ -24,14 +24,14 @@ var ( ) type extraDataForTx struct { - supportPara bool - fee sdk.Coins - isEvm bool - isE2C bool - from string - to string - stdTx sdk.Tx - decodeErr error + supportPara bool + fee sdk.Coins + isEvm bool + needUpdateTXCounter bool + from string + to string + stdTx sdk.Tx + decodeErr error } type txWithIndex struct { @@ -71,15 +71,15 @@ func (app *BaseApp) getExtraDataByTxs(txs [][]byte) { app.blockDataCache.SetTx(txBytes, tx) } - coin, isEvm, isE2C, s, toAddr, _, supportPara := app.getTxFeeAndFromHandler(app.getContextForTx(runTxModeDeliver, txBytes), tx) + coin, isEvm, needUpdateTXCounter, s, toAddr, _, supportPara := app.getTxFeeAndFromHandler(app.getContextForTx(runTxModeDeliver, txBytes), tx) para.extraTxsInfo[index] = &extraDataForTx{ - supportPara: supportPara, - fee: coin, - isEvm: isEvm, - isE2C: isE2C, - from: s, - to: toAddr, - stdTx: tx, + supportPara: supportPara, + fee: coin, + isEvm: isEvm, + needUpdateTXCounter: needUpdateTXCounter, + from: s, + to: toAddr, + stdTx: tx, } wg.Done() } @@ -129,49 +129,45 @@ func Union(x string, yString string) { // calGroup cal group by txs func (app *BaseApp) calGroup() { - para := app.parallelTxManage + pm := app.parallelTxManage rootAddr = make(map[string]string, 0) - para.cosmosTxIndexInBlock = 0 - for index, tx := range para.extraTxsInfo { + pm.cosmosTxIndexInBlock = 0 + for index, tx := range pm.extraTxsInfo { if tx.supportPara { //evmTx & wasmTx Union(tx.from, tx.to) } else { - para.haveCosmosTxInBlock = true app.parallelTxManage.putResult(index, &executeResult{paraMsg: &sdk.ParaMsg{}, msIsNil: true}) } - if (!tx.isEvm && tx.supportPara) || tx.isE2C { - // means wasm or e2c tx - para.haveCosmosTxInBlock = true + if tx.needUpdateTXCounter { + pm.txIndexMpUpdateTXCounter[index] = true + pm.txByteMpCosmosIndex[string(pm.txs[index])] = pm.cosmosTxIndexInBlock + pm.cosmosTxIndexInBlock++ } - if !tx.isEvm || tx.isE2C { - para.txByteMpCosmosIndex[string(para.txs[index])] = para.cosmosTxIndexInBlock - para.cosmosTxIndexInBlock++ - } } addrToID := make(map[string]int, 0) - for index, txInfo := range para.extraTxsInfo { + for index, txInfo := range pm.extraTxsInfo { if !txInfo.supportPara { continue } rootAddr := Find(txInfo.from) id, exist := addrToID[rootAddr] if !exist { - id = len(para.groupList) + id = len(pm.groupList) addrToID[rootAddr] = id } - para.groupList[id] = append(para.groupList[id], index) - para.txIndexWithGroup[index] = id + pm.groupList[id] = append(pm.groupList[id], index) + pm.txIndexWithGroup[index] = id } - groupSize := len(para.groupList) + groupSize := len(pm.groupList) for groupIndex := 0; groupIndex < groupSize; groupIndex++ { - list := para.groupList[groupIndex] + list := pm.groupList[groupIndex] for index := 0; index < len(list); index++ { if index+1 <= len(list)-1 { app.parallelTxManage.nextTxInGroup[list[index]] = list[index+1] @@ -247,7 +243,7 @@ func (app *BaseApp) runTxs() []*abci.ResponseDeliverTx { break } isReRun := false - if pm.isConflict(res) || overFlow(currentGas, res.resp.GasUsed, maxGas) { + if pm.isConflict(res) || overFlow(currentGas, res.resp.GasUsed, maxGas) || pm.haveAnteErrTx { rerunIdx++ isReRun = true // conflict rerun tx @@ -256,8 +252,10 @@ func (app *BaseApp) runTxs() []*abci.ResponseDeliverTx { } res = app.deliverTxWithCache(pm.upComingTxIndex) } + if res.paraMsg.AnteErr != nil { res.msIsNil = true + pm.handleAnteErrTx(res.paraMsg.NeedUpdateTXCounter) } pm.deliverTxs[pm.upComingTxIndex] = &res.resp @@ -268,7 +266,7 @@ func (app *BaseApp) runTxs() []*abci.ResponseDeliverTx { app.deliverState.ctx.BlockGasMeter().ConsumeGas(sdk.Gas(res.resp.GasUsed), "unexpected error") pm.blockGasMeterMu.Unlock() - pm.SetCurrentIndex(pm.upComingTxIndex, res) + pm.SetCurrentIndexRes(pm.upComingTxIndex, res) if !res.msIsNil { pm.currTxFee = pm.currTxFee.Add(pm.extraTxsInfo[pm.upComingTxIndex].fee.Sub(pm.finalResult[pm.upComingTxIndex].paraMsg.RefundFee)...) @@ -320,7 +318,7 @@ func (app *BaseApp) runTxs() []*abci.ResponseDeliverTx { ctx, _ := app.cacheTxContext(app.getContextForTx(runTxModeDeliver, []byte{}), []byte{}) ctx.SetMultiStore(app.parallelTxManage.cms) - if app.parallelTxManage.haveCosmosTxInBlock { + if app.parallelTxManage.NeedUpdateTXCounter() { app.updateCosmosTxCount(ctx, app.parallelTxManage.cosmosTxIndexInBlock-1) } @@ -454,16 +452,19 @@ func newExecuteResult(r abci.ResponseDeliverTx, ms sdk.CacheMultiStore, counter } type parallelTxManager struct { - blockHeight int64 - groupTasks []*groupTask - blockGasMeterMu sync.Mutex - haveCosmosTxInBlock bool - isAsyncDeliverTx bool - txs [][]byte - txSize int - alreadyEnd bool - cosmosTxIndexInBlock int - txByteMpCosmosIndex map[string]int + blockHeight int64 + groupTasks []*groupTask + blockGasMeterMu sync.Mutex + isAsyncDeliverTx bool + txs [][]byte + txSize int + alreadyEnd bool + + cosmosTxIndexInBlock int + txByteMpCMIndexLock sync.RWMutex + txByteMpCosmosIndex map[string]int + txIndexMpUpdateTXCounter []bool + haveAnteErrTx bool resultCh chan int resultCb func(data int) @@ -634,6 +635,8 @@ func newParallelTxManager() *parallelTxManager { txIndexWithGroup: make(map[int]int), resultCh: make(chan int, maxTxResultInChan), + txByteMpCMIndexLock: sync.RWMutex{}, + blockMpCache: newCacheRWSetList(), chainMpCache: newCacheRWSetList(), blockMultiStores: newCacheMultiStoreList(), @@ -764,7 +767,6 @@ func (pm *parallelTxManager) init(txs [][]byte, blockHeight int64, deliverStateM txSize := len(txs) pm.blockHeight = blockHeight pm.groupTasks = make([]*groupTask, 0) - pm.haveCosmosTxInBlock = false pm.isAsyncDeliverTx = true pm.txs = txs pm.txSize = txSize @@ -788,6 +790,8 @@ func (pm *parallelTxManager) init(txs [][]byte, blockHeight int64, deliverStateM pm.txByteMpCosmosIndex = make(map[string]int, 0) pm.nextTxInGroup = make(map[int]int) + pm.haveAnteErrTx = false + pm.txIndexMpUpdateTXCounter = make([]bool, txSize) pm.extraTxsInfo = make([]*extraDataForTx, txSize) pm.txReps = make([]*executeResult, txSize) pm.finalResult = make([]*executeResult, txSize) @@ -815,7 +819,7 @@ func (pm *parallelTxManager) getParentMsByTxIndex(txIndex int) (sdk.CacheMultiSt return ms, useCurrent } -func (pm *parallelTxManager) SetCurrentIndex(txIndex int, res *executeResult) { +func (pm *parallelTxManager) SetCurrentIndexRes(txIndex int, res *executeResult) { if res.msIsNil { return } @@ -837,3 +841,29 @@ func (pm *parallelTxManager) SetCurrentIndex(txIndex int, res *executeResult) { } } + +func (pm *parallelTxManager) NeedUpdateTXCounter() bool { + res := false + for _, v := range pm.txIndexMpUpdateTXCounter { + res = res || v + } + return res +} + +// When an AnteErr tx is encountered, this tx will be discarded, +// and the cosmosIndex of the remaining tx needs to be corrected. +func (pm *parallelTxManager) handleAnteErrTx(needUpdateTXCounter bool) { + pm.haveAnteErrTx = true + pm.txIndexMpUpdateTXCounter[pm.upComingTxIndex] = false + + if needUpdateTXCounter { + pm.cosmosTxIndexInBlock-- + pm.txByteMpCMIndexLock.Lock() + for index, tx := range pm.txs { + if _, ok := pm.txByteMpCosmosIndex[string(tx)]; ok && index > pm.upComingTxIndex { + pm.txByteMpCosmosIndex[string(tx)]-- + } + } + pm.txByteMpCMIndexLock.Unlock() + } +} diff --git a/libs/cosmos-sdk/types/context.go b/libs/cosmos-sdk/types/context.go index 8016d61ddc..7a10eab2de 100644 --- a/libs/cosmos-sdk/types/context.go +++ b/libs/cosmos-sdk/types/context.go @@ -2,17 +2,17 @@ package types import ( "context" - "github.com/ethereum/go-ethereum/core/vm" "sync" "time" + "github.com/ethereum/go-ethereum/core/vm" "github.com/gogo/protobuf/proto" - "github.com/okex/exchain/libs/system/trace" - abci "github.com/okex/exchain/libs/tendermint/abci/types" - "github.com/okex/exchain/libs/tendermint/libs/log" "github.com/okex/exchain/libs/cosmos-sdk/store/gaskv" stypes "github.com/okex/exchain/libs/cosmos-sdk/store/types" + "github.com/okex/exchain/libs/system/trace" + abci "github.com/okex/exchain/libs/tendermint/abci/types" + "github.com/okex/exchain/libs/tendermint/libs/log" ) /* @@ -92,7 +92,8 @@ func (c *Context) IsDeliverWithSerial() bool { } func (c *Context) UseParamCache() bool { - return c.isDeliverWithSerial || (c.paraMsg != nil && !c.paraMsg.HaveCosmosTxInBlock) || c.checkTx + // NeedUpdateTXCounter of E2C tx also is true. + return c.isDeliverWithSerial || (c.paraMsg != nil && !c.paraMsg.NeedUpdateTXCounter) || c.checkTx } func (c *Context) IsCheckTx() bool { return c.checkTx } diff --git a/libs/cosmos-sdk/types/utils.go b/libs/cosmos-sdk/types/utils.go index dca8183929..7403b06140 100644 --- a/libs/cosmos-sdk/types/utils.go +++ b/libs/cosmos-sdk/types/utils.go @@ -96,7 +96,7 @@ func NewDB(name, dir string) (db dbm.DB, err error) { type ParaMsg struct { UseCurrentState bool - HaveCosmosTxInBlock bool + NeedUpdateTXCounter bool AnteErr error RefundFee Coins LogIndex int diff --git a/x/wasm/keeper/ante.go b/x/wasm/keeper/ante.go index a38077d69d..e3aca72ce3 100644 --- a/x/wasm/keeper/ante.go +++ b/x/wasm/keeper/ante.go @@ -3,10 +3,8 @@ package keeper import ( "encoding/binary" - types2 "github.com/okex/exchain/libs/tendermint/types" - sdk "github.com/okex/exchain/libs/cosmos-sdk/types" - + tmtypes "github.com/okex/exchain/libs/tendermint/types" "github.com/okex/exchain/x/wasm/types" ) @@ -30,7 +28,7 @@ func NewCountTXDecorator(storeKey sdk.StoreKey) *CountTXDecorator { // The ante handler passes the counter value via sdk.Context upstream. See `types.TXCounter(ctx)` to read the value. // Simulations don't get a tx counter value assigned. func (a CountTXDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - if simulate || !types2.HigherThanEarth(ctx.BlockHeight()) { + if simulate || !tmtypes.HigherThanEarth(ctx.BlockHeight()) { return next(ctx, tx, simulate) } currentGasmeter := ctx.GasMeter() @@ -108,7 +106,9 @@ func (d LimitSimulationGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu } func UpdateTxCount(ctx sdk.Context, storeKey sdk.StoreKey, txCount int) { - store := ctx.KVStore(storeKey) - currentHeight := ctx.BlockHeight() - store.Set(types.TXCounterPrefix, encodeHeightCounter(currentHeight, uint32(txCount+1))) + if tmtypes.HigherThanEarth(ctx.BlockHeight()) { + store := ctx.KVStore(storeKey) + currentHeight := ctx.BlockHeight() + store.Set(types.TXCounterPrefix, encodeHeightCounter(currentHeight, uint32(txCount+1))) + } } From c45db7dfbcfa6b11d428a4df7fdc8201fdaf1593 Mon Sep 17 00:00:00 2001 From: FineKe <530362991@qq.com> Date: Tue, 11 Jul 2023 15:54:03 +0800 Subject: [PATCH 3/9] add auto start from snapshot (#3193) Co-authored-by: KamiD <44460798+KamiD@users.noreply.github.com> --- app/app.go | 3 + app/start_from_snapshot.go | 199 ++++++++++++++++++++++++ go.mod | 3 + go.sum | 4 + libs/cosmos-sdk/server/start.go | 3 +- libs/cosmos-sdk/server/start_okchain.go | 3 + 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 app/start_from_snapshot.go diff --git a/app/app.go b/app/app.go index 12b75d29d4..08e09ef0a5 100644 --- a/app/app.go +++ b/app/app.go @@ -2,6 +2,7 @@ package app import ( "fmt" + "github.com/okex/exchain/libs/cosmos-sdk/client/flags" "io" "os" "runtime/debug" @@ -958,6 +959,8 @@ func NewAccNonceHandler(ak auth.AccountKeeper) sdk.AccNonceHandler { } func PreRun(ctx *server.Context, cmd *cobra.Command) error { + prepareSnapshotDataIfNeed(viper.GetString(server.FlagStartFromSnapshot), viper.GetString(flags.FlagHome), ctx.Logger) + // check start flag conflicts err := sanity.CheckStart() if err != nil { diff --git a/app/start_from_snapshot.go b/app/start_from_snapshot.go new file mode 100644 index 0000000000..2bf4eea660 --- /dev/null +++ b/app/start_from_snapshot.go @@ -0,0 +1,199 @@ +package app + +import ( + "archive/tar" + "bytes" + "fmt" + "github.com/klauspost/pgzip" + "github.com/okex/exchain/libs/cosmos-sdk/types/errors" + "github.com/okex/exchain/libs/tendermint/libs/log" + "io" + "io/ioutil" + "net/url" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "time" +) + +func prepareSnapshotDataIfNeed(snapshotURL string, home string, logger log.Logger) { + if snapshotURL == "" { + return + } + + snapshotHome := filepath.Join(home, ".download_snapshots") + + // check whether the snapshot file has been downloaded + byteData, err := os.ReadFile(filepath.Join(snapshotHome, ".record")) + if err == nil && strings.Contains(string(byteData), snapshotURL) { + return + } + + if _, err := url.Parse(snapshotURL); err != nil { + panic(errors.Wrap(err, "invalid snapshot URL")) + } + + // download snapshot + snapshotFile, err := downloadSnapshot(snapshotURL, snapshotHome, logger) + if err != nil { + panic(err) + } + + // uncompress snapshot + logger.Info("start to uncompress snapshot") + if err := extractTarGz(snapshotFile, snapshotHome); err != nil { + panic(err) + } + + // delete damaged data + logger.Info("start to delete damaged data") + if err := os.RemoveAll(filepath.Join(home, "data")); err != nil { + panic(err) + } + + // move snapshot data + logger.Info("start to move snapshot data") + if err := moveDir(filepath.Join(snapshotHome, "data"), filepath.Join(home, "data")); err != nil { + panic(err) + } + + os.Remove(snapshotFile) + + os.WriteFile(filepath.Join(snapshotHome, ".record"), []byte(snapshotURL+"\n"), 0644) + + logger.Info("snapshot data is ready, start node soon!") +} + +func downloadSnapshot(url, outputPath string, logger log.Logger) (string, error) { + // create dir + _, err := os.Stat(outputPath) + if err != nil { + os.MkdirAll(outputPath, 0755) + } + + fileName := url[strings.LastIndex(url, "/")+1:] + targetFile := filepath.Join(outputPath, fileName) + + // check file exists + if _, err := os.Stat(targetFile); err == nil { + os.Remove(targetFile) + } + + var stdoutProcessStatus bytes.Buffer + + axel := exec.Command("axel", "-n", fmt.Sprintf("%d", runtime.NumCPU()), "-o", targetFile, url) + axel.Stdout = io.MultiWriter(ioutil.Discard, &stdoutProcessStatus) + done := make(chan struct{}) + defer close(done) + + // print download detail + go func() { + tick := time.NewTicker(time.Millisecond * 50) + defer tick.Stop() + for { + select { + case <-done: + return + case <-tick.C: + bts := make([]byte, stdoutProcessStatus.Len()) + stdoutProcessStatus.Read(bts) + logger.Info(string(bts)) + } + } + }() + + // run and wait + err = axel.Run() + if err != nil { + return "", err + } + + return targetFile, nil +} + +func extractTarGz(tarGzFile, destinationDir string) error { + // open .tar.gz + file, err := os.Open(tarGzFile) + if err != nil { + return err + } + defer file.Close() + + // use gzip.Reader + gzReader, err := pgzip.NewReaderN(file, 1<<22, runtime.NumCPU()) + if err != nil { + return err + } + defer gzReader.Close() + + // create tar.Reader + tarReader := tar.NewReader(gzReader) + + // uncompress file back to back + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if header == nil { + continue + } + target := filepath.Join(destinationDir, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + err = os.MkdirAll(target, 0755) + if err != nil { + return err + } + case tar.TypeReg: + parent := filepath.Dir(target) + err = os.MkdirAll(parent, 0755) + if err != nil { + return err + } + + file, err := os.Create(target) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, tarReader) + if err != nil { + return err + } + } + } + + return nil +} + +func moveDir(sourceDir, destinationDir string) error { + sourceInfo, err := os.Stat(sourceDir) + if err != nil { + return err + } + + if !sourceInfo.IsDir() { + return fmt.Errorf("%s isn't dir", sourceDir) + } + + _, err = os.Stat(destinationDir) + if err == nil { + return fmt.Errorf("dest dir %s exists", destinationDir) + } + + // move + err = os.Rename(sourceDir, destinationDir) + if err != nil { + return err + } + + return nil +} diff --git a/go.mod b/go.mod index 9c468ea80e..6e47eddf8a 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/jmhodges/levigo v1.0.0 github.com/json-iterator/go v1.1.12 + github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada github.com/libp2p/go-buffer-pool v0.1.0 github.com/magiconair/properties v1.8.6 github.com/mattn/go-isatty v0.0.14 @@ -136,6 +137,8 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jonboulle/clockwork v0.2.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect diff --git a/go.sum b/go.sum index b05d7d5878..ee866ca067 100644 --- a/go.sum +++ b/go.sum @@ -518,9 +518,13 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada h1:3L+neHp83cTjegPdCiOxVOJtRIy7/8RldvMTsyPYH10= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/libs/cosmos-sdk/server/start.go b/libs/cosmos-sdk/server/start.go index ee63cd74fb..055e4f84e0 100644 --- a/libs/cosmos-sdk/server/start.go +++ b/libs/cosmos-sdk/server/start.go @@ -72,7 +72,8 @@ const ( FlagFastSyncGap = "fastsync-gap" - FlagEventBlockTime = "event-block-time" + FlagEventBlockTime = "event-block-time" + FlagStartFromSnapshot = "start-from-snapshot" ) // StartCmd runs the service passed in, either stand-alone or in-process with diff --git a/libs/cosmos-sdk/server/start_okchain.go b/libs/cosmos-sdk/server/start_okchain.go index 687b258a1b..e6e7d33db8 100644 --- a/libs/cosmos-sdk/server/start_okchain.go +++ b/libs/cosmos-sdk/server/start_okchain.go @@ -247,6 +247,7 @@ func RegisterServerFlags(cmd *cobra.Command) *cobra.Command { viper.BindPFlag(FlagEvmImportMode, cmd.Flags().Lookup(FlagEvmImportMode)) viper.BindPFlag(FlagEvmImportPath, cmd.Flags().Lookup(FlagEvmImportPath)) viper.BindPFlag(FlagGoroutineNum, cmd.Flags().Lookup(FlagGoroutineNum)) + viper.BindPFlag(FlagStartFromSnapshot, cmd.Flags().Lookup(FlagStartFromSnapshot)) cmd.Flags().Int(state.FlagDeliverTxsExecMode, 0, "Execution mode for deliver txs, (0:serial[default], 1:deprecated, 2:parallel)") cmd.Flags().Bool(state.FlagEnableConcurrency, false, "Enable concurrency for deliver txs") @@ -275,6 +276,8 @@ func RegisterServerFlags(cmd *cobra.Command) *cobra.Command { cmd.Flags().Int64(FlagCommitGapHeight, 100, "Block interval to commit cached data into db, affects iavl & mpt") cmd.Flags().Int64(FlagFastSyncGap, 20, "Block height interval to switch fast-sync mode") + cmd.Flags().String(FlagStartFromSnapshot, "", "Snapshot URL which uses to start node") + cmd.Flags().MarkHidden(FlagStartFromSnapshot) return cmd } From 68fd8c1f17871537de3419b87405046f6f4d13db Mon Sep 17 00:00:00 2001 From: KamiD <44460798+KamiD@users.noreply.github.com> Date: Tue, 11 Jul 2023 16:15:37 +0800 Subject: [PATCH 4/9] Bump version to v1.7.8 (#3204) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c377d6117f..e4bd0ff76a 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ IGNORE_CHECK_GO=false install_rocksdb_version:=$(ROCKSDB_VERSION) -Version=v1.7.7 +Version=v1.7.8 CosmosSDK=v0.39.2 Tendermint=v0.33.9 Iavl=v0.14.3 From 39c86b1ba14a45591b93386d7c67462a76bcb92c Mon Sep 17 00:00:00 2001 From: Leo <52782564+LeoGuo621@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:29:55 +0800 Subject: [PATCH 5/9] fix smb caused by evm AnteErr tx (#3205) --- libs/cosmos-sdk/baseapp/baseapp.go | 2 +- libs/cosmos-sdk/baseapp/baseapp_parallel.go | 6 +++--- libs/cosmos-sdk/types/context.go | 4 ++-- libs/cosmos-sdk/types/utils.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/cosmos-sdk/baseapp/baseapp.go b/libs/cosmos-sdk/baseapp/baseapp.go index 4af0353f1a..40dfa49cb9 100644 --- a/libs/cosmos-sdk/baseapp/baseapp.go +++ b/libs/cosmos-sdk/baseapp/baseapp.go @@ -703,7 +703,7 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context // and there is a small probability that NeedUpdateTXCounter() will be wrong // due to concurrent reading and writing of pm.txIndexMpUpdateTXCounter (slice), // but such tx will be rerun, so this case can be ignored. - NeedUpdateTXCounter: app.parallelTxManage.NeedUpdateTXCounter(), + HaveCosmosTxInBlock: app.parallelTxManage.NeedUpdateTXCounter(), CosmosIndexInBlock: app.parallelTxManage.txByteMpCosmosIndex[string(txBytes)], }) app.parallelTxManage.txByteMpCMIndexLock.RUnlock() diff --git a/libs/cosmos-sdk/baseapp/baseapp_parallel.go b/libs/cosmos-sdk/baseapp/baseapp_parallel.go index 81435b9820..2baeb56d8b 100644 --- a/libs/cosmos-sdk/baseapp/baseapp_parallel.go +++ b/libs/cosmos-sdk/baseapp/baseapp_parallel.go @@ -255,7 +255,7 @@ func (app *BaseApp) runTxs() []*abci.ResponseDeliverTx { if res.paraMsg.AnteErr != nil { res.msIsNil = true - pm.handleAnteErrTx(res.paraMsg.NeedUpdateTXCounter) + pm.handleAnteErrTx(pm.txIndexMpUpdateTXCounter[pm.upComingTxIndex]) } pm.deliverTxs[pm.upComingTxIndex] = &res.resp @@ -852,11 +852,11 @@ func (pm *parallelTxManager) NeedUpdateTXCounter() bool { // When an AnteErr tx is encountered, this tx will be discarded, // and the cosmosIndex of the remaining tx needs to be corrected. -func (pm *parallelTxManager) handleAnteErrTx(needUpdateTXCounter bool) { +func (pm *parallelTxManager) handleAnteErrTx(curTxNeedUpdateTXCounter bool) { pm.haveAnteErrTx = true pm.txIndexMpUpdateTXCounter[pm.upComingTxIndex] = false - if needUpdateTXCounter { + if curTxNeedUpdateTXCounter { pm.cosmosTxIndexInBlock-- pm.txByteMpCMIndexLock.Lock() for index, tx := range pm.txs { diff --git a/libs/cosmos-sdk/types/context.go b/libs/cosmos-sdk/types/context.go index 7a10eab2de..69ad5af29f 100644 --- a/libs/cosmos-sdk/types/context.go +++ b/libs/cosmos-sdk/types/context.go @@ -92,8 +92,8 @@ func (c *Context) IsDeliverWithSerial() bool { } func (c *Context) UseParamCache() bool { - // NeedUpdateTXCounter of E2C tx also is true. - return c.isDeliverWithSerial || (c.paraMsg != nil && !c.paraMsg.NeedUpdateTXCounter) || c.checkTx + // c.paraMsg.HaveCosmosTxInBlock is also true when there are only E2C txs in a block + return c.isDeliverWithSerial || (c.paraMsg != nil && !c.paraMsg.HaveCosmosTxInBlock) || c.checkTx } func (c *Context) IsCheckTx() bool { return c.checkTx } diff --git a/libs/cosmos-sdk/types/utils.go b/libs/cosmos-sdk/types/utils.go index 7403b06140..dca8183929 100644 --- a/libs/cosmos-sdk/types/utils.go +++ b/libs/cosmos-sdk/types/utils.go @@ -96,7 +96,7 @@ func NewDB(name, dir string) (db dbm.DB, err error) { type ParaMsg struct { UseCurrentState bool - NeedUpdateTXCounter bool + HaveCosmosTxInBlock bool AnteErr error RefundFee Coins LogIndex int From c2fa4a0fe43f2ca74a94745e5f919455c2910175 Mon Sep 17 00:00:00 2001 From: "xingqiang.yuan" Date: Thu, 20 Jul 2023 11:05:43 +0800 Subject: [PATCH 6/9] optimize gas estimation performance (#2879) * add commit mutex * add pgu threshold * add regression of hgu bounding * fix ut * pack one tx at least * simulation gas wanted must less than gas limit * persist estimated gas and refactor pgu * record hgu block num * add pgu logger * fix bug * add ut * fix ut * update * fix bug of simulation * temp * update * merge dev * pgu twice --------- Co-authored-by: KamiD <44460798+KamiD@users.noreply.github.com> --- app/config/config.go | 54 +++++++ libs/cosmos-sdk/baseapp/baseapp.go | 27 +++- libs/cosmos-sdk/baseapp/gasuseddb.go | 99 +++++++----- libs/cosmos-sdk/baseapp/gasuseddb_test.go | 118 ++++++++++++++ libs/cosmos-sdk/baseapp/hgu.pb.go | 102 ++++++++++++ libs/cosmos-sdk/baseapp/proto/hgu.proto | 9 ++ .../cmd/tendermint/commands/run_node.go | 14 ++ .../config/dynamic_config_okchain.go | 15 ++ libs/tendermint/global/commit_mutex.go | 30 ++++ libs/tendermint/mempool/clist_mempool.go | 145 ++++++++++++------ libs/tendermint/mempool/clist_mempool_test.go | 4 +- libs/tendermint/mempool/mempool.go | 10 +- libs/tendermint/mempool/pgu.go | 52 +++++++ libs/tendermint/state/execution.go | 4 + 14 files changed, 592 insertions(+), 91 deletions(-) create mode 100644 libs/cosmos-sdk/baseapp/gasuseddb_test.go create mode 100644 libs/cosmos-sdk/baseapp/hgu.pb.go create mode 100644 libs/cosmos-sdk/baseapp/proto/hgu.proto create mode 100644 libs/tendermint/global/commit_mutex.go create mode 100644 libs/tendermint/mempool/pgu.go diff --git a/app/config/config.go b/app/config/config.go index 15f55a62dd..ac4cfbe88d 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -49,8 +49,14 @@ type OecConfig struct { maxGasUsedPerBlock int64 // mempool.enable-pgu enablePGU bool + // mempool.pgu-percentage-threshold + pguPercentageThreshold int64 + // mempool.pgu-concurrency + pguConcurrency int // mempool.pgu-adjustment pguAdjustment float64 + // mempool.pgu-persist + pguPersist bool // mempool.node_key_whitelist nodeKeyWhitelist []string //mempool.check_tx_cost @@ -141,7 +147,10 @@ const ( FlagMaxTxNumPerBlock = "mempool.max_tx_num_per_block" FlagMaxGasUsedPerBlock = "mempool.max_gas_used_per_block" FlagEnablePGU = "mempool.enable-pgu" + FlagPGUPercentageThreshold = "mempool.pgu-percentage-threshold" + FlagPGUConcurrency = "mempool.pgu-concurrency" FlagPGUAdjustment = "mempool.pgu-adjustment" + FlagPGUPersist = "mempool.pgu-persist" FlagNodeKeyWhitelist = "mempool.node_key_whitelist" FlagMempoolCheckTxCost = "mempool.check_tx_cost" FlagMempoolEnableDeleteMinGPTx = "mempool.enable_delete_min_gp_tx" @@ -287,7 +296,10 @@ func (c *OecConfig) loadFromConfig() { c.SetPendingPoolBlacklist(viper.GetString(FlagPendingPoolBlacklist)) c.SetMaxGasUsedPerBlock(viper.GetInt64(FlagMaxGasUsedPerBlock)) c.SetEnablePGU(viper.GetBool(FlagEnablePGU)) + c.SetPGUPercentageThreshold(viper.GetInt64(FlagPGUPercentageThreshold)) + c.SetPGUConcurrency(viper.GetInt(FlagPGUConcurrency)) c.SetPGUAdjustment(viper.GetFloat64(FlagPGUAdjustment)) + c.SetPGUPersist(viper.GetBool(FlagPGUPersist)) c.SetGasLimitBuffer(viper.GetUint64(FlagGasLimitBuffer)) c.SetEnableDynamicGp(viper.GetBool(FlagEnableDynamicGp)) @@ -504,12 +516,30 @@ func (c *OecConfig) updateFromKVStr(k, v string) { return } c.SetEnablePGU(r) + case FlagPGUPercentageThreshold: + r, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return + } + c.SetPGUPercentageThreshold(r) + case FlagPGUConcurrency: + r, err := strconv.Atoi(v) + if err != nil { + return + } + c.SetPGUConcurrency(r) case FlagPGUAdjustment: r, err := strconv.ParseFloat(v, 64) if err != nil { return } c.SetPGUAdjustment(r) + case FlagPGUPersist: + r, err := strconv.ParseBool(v) + if err != nil { + return + } + c.SetPGUPersist(r) case FlagGasLimitBuffer: r, err := strconv.ParseUint(v, 10, 64) if err != nil { @@ -834,6 +864,22 @@ func (c *OecConfig) SetEnablePGU(value bool) { c.enablePGU = value } +func (c *OecConfig) GetPGUPercentageThreshold() int64 { + return c.pguPercentageThreshold +} + +func (c *OecConfig) SetPGUPercentageThreshold(value int64) { + c.pguPercentageThreshold = value +} + +func (c *OecConfig) GetPGUConcurrency() int { + return c.pguConcurrency +} + +func (c *OecConfig) SetPGUConcurrency(value int) { + c.pguConcurrency = value +} + func (c *OecConfig) GetPGUAdjustment() float64 { return c.pguAdjustment } @@ -842,6 +888,14 @@ func (c *OecConfig) SetPGUAdjustment(value float64) { c.pguAdjustment = value } +func (c *OecConfig) GetPGUPersist() bool { + return c.pguPersist +} + +func (c *OecConfig) SetPGUPersist(value bool) { + c.pguPersist = value +} + func (c *OecConfig) GetGasLimitBuffer() uint64 { return c.gasLimitBuffer } diff --git a/libs/cosmos-sdk/baseapp/baseapp.go b/libs/cosmos-sdk/baseapp/baseapp.go index 40dfa49cb9..fc7c7a0600 100644 --- a/libs/cosmos-sdk/baseapp/baseapp.go +++ b/libs/cosmos-sdk/baseapp/baseapp.go @@ -1019,25 +1019,42 @@ func (app *BaseApp) GetRealTxFromRawTx(rawTx tmtypes.Tx) abci.TxEssentials { return nil } -func (app *BaseApp) GetTxHistoryGasUsed(rawTx tmtypes.Tx) int64 { +func (app *BaseApp) GetTxHistoryGasUsed(rawTx tmtypes.Tx, gasLimit int64) (int64, bool) { tx, err := app.txDecoder(rawTx) if err != nil { - return -1 + return -1, false } txFnSig, toDeployContractSize := tx.GetTxFnSignatureInfo() if txFnSig == nil { - return -1 + return -1, false } hgu := InstanceOfHistoryGasUsedRecordDB().GetHgu(txFnSig) + if hgu == nil { + return -1, false + } + precise := true + if hgu.BlockNum < preciseBlockNum || + (hgu.MaxGas-hgu.MovingAverageGas)*100/hgu.MovingAverageGas > cfg.DynamicConfig.GetPGUPercentageThreshold() || + (hgu.MovingAverageGas-hgu.MinGas)*100/hgu.MinGas > cfg.DynamicConfig.GetPGUPercentageThreshold() { + precise = false + } + var gasWanted int64 if toDeployContractSize > 0 { // if deploy contract case, the history gas used value is unit gas used - return hgu*int64(toDeployContractSize) + int64(1000) + gasWanted = hgu.MovingAverageGas*int64(toDeployContractSize) + int64(1000) + } else { + gasWanted = hgu.MovingAverageGas + } + + // hgu gas can not be greater than gasLimit + if gasWanted > gasLimit { + gasWanted = gasLimit } - return hgu + return gasWanted, precise } func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter } diff --git a/libs/cosmos-sdk/baseapp/gasuseddb.go b/libs/cosmos-sdk/baseapp/gasuseddb.go index a224a6047e..3ca9936966 100644 --- a/libs/cosmos-sdk/baseapp/gasuseddb.go +++ b/libs/cosmos-sdk/baseapp/gasuseddb.go @@ -1,10 +1,11 @@ package baseapp import ( - "encoding/binary" + "log" "path/filepath" "sync" + "github.com/gogo/protobuf/proto" lru "github.com/hashicorp/golang-lru" "github.com/okex/exchain/libs/cosmos-sdk/client/flags" sdk "github.com/okex/exchain/libs/cosmos-sdk/types" @@ -17,13 +18,15 @@ const ( HistoryGasUsedDBName = "hgu" FlagGasUsedFactor = "gu_factor" + preciseBlockNum = 20 ) var ( - once sync.Once - GasUsedFactor = 0.4 - jobQueueLen = 10 - cacheSize = 10000 + once sync.Once + GasUsedFactor = 0.4 + regressionFactor = 0.05 + jobQueueLen = 10 + cacheSize = 10000 historyGasUsedRecordDB HistoryGasUsedRecordDB ) @@ -35,7 +38,7 @@ type gasKey struct { type HistoryGasUsedRecordDB struct { latestGuMtx sync.Mutex - latestGu map[string]int64 + latestGu map[string][]int64 cache *lru.Cache guDB db.DB @@ -46,7 +49,7 @@ func InstanceOfHistoryGasUsedRecordDB() *HistoryGasUsedRecordDB { once.Do(func() { cache, _ := lru.New(cacheSize) historyGasUsedRecordDB = HistoryGasUsedRecordDB{ - latestGu: make(map[string]int64), + latestGu: make(map[string][]int64), cache: cache, guDB: initDb(), jobQueue: make(chan func(), jobQueueLen), @@ -58,13 +61,13 @@ func InstanceOfHistoryGasUsedRecordDB() *HistoryGasUsedRecordDB { func (h *HistoryGasUsedRecordDB) UpdateGasUsed(key []byte, gasUsed int64) { h.latestGuMtx.Lock() - h.latestGu[string(key)] = gasUsed + h.latestGu[string(key)] = append(h.latestGu[string(key)], gasUsed) h.latestGuMtx.Unlock() } -func (h *HistoryGasUsedRecordDB) GetHgu(key []byte) int64 { +func (h *HistoryGasUsedRecordDB) GetHgu(key []byte) *HguRecord { hgu, cacheHit := h.getHgu(key) - if !cacheHit && hgu != -1 { + if hgu != nil && !cacheHit { // add to cache before returning hgu h.cache.Add(string(key), hgu) } @@ -75,43 +78,71 @@ func (h *HistoryGasUsedRecordDB) FlushHgu() { if len(h.latestGu) == 0 { return } - latestGasKeys := make([]gasKey, len(h.latestGu)) - index := 0 - for key, gas := range h.latestGu { - latestGasKeys[index] = gasKey{ - gas: gas, + latestGasKeys := make([]gasKey, 0, len(h.latestGu)) + for key, allGas := range h.latestGu { + latestGasKeys = append(latestGasKeys, gasKey{ + gas: meanGas(allGas), key: key, - } - index++ + }) delete(h.latestGu, key) } h.jobQueue <- func() { h.flushHgu(latestGasKeys...) } // closure } -func (h *HistoryGasUsedRecordDB) getHgu(key []byte) (hgu int64, fromCache bool) { +func (h *HistoryGasUsedRecordDB) getHgu(key []byte) (hgu *HguRecord, fromCache bool) { v, ok := h.cache.Get(string(key)) if ok { - return v.(int64), true + return v.(*HguRecord), true } data, err := h.guDB.Get(key) if err != nil || len(data) == 0 { - return -1, false + return nil, false } - return bytesToInt64(data), false + var r HguRecord + err = proto.Unmarshal(data, &r) + if err != nil { + return nil, false + } + return &r, false } func (h *HistoryGasUsedRecordDB) flushHgu(gks ...gasKey) { for _, gk := range gks { hgu, cacheHit := h.getHgu([]byte(gk.key)) - // avgGas = 0.4 * newGas + 0.6 * oldGas.The value of wasm store contract is too small and need to be rounded up. - avgGas := int64(GasUsedFactor*float64(gk.gas) + (1.0-GasUsedFactor)*float64(hgu) + 0.6) - // add to cache if hit - if cacheHit { - h.cache.Add(gk.key, avgGas) + if hgu == nil { + hgu = &HguRecord{ + MaxGas: gk.gas, + MinGas: gk.gas, + MovingAverageGas: gk.gas, + } + } else { + // MovingAverageGas = 0.4 * newGas + 0.6 * oldMovingAverageGas + hgu.MovingAverageGas = int64(GasUsedFactor*float64(gk.gas) + (1.0-GasUsedFactor)*float64(hgu.MovingAverageGas)) + // MaxGas = 0.05 * MovingAverageGas + 0.95 * oldMaxGas + hgu.MaxGas = int64(regressionFactor*float64(hgu.MovingAverageGas) + (1.0-regressionFactor)*float64(hgu.MaxGas)) + // MinGas = 0.05 * MovingAverageGas + 0.95 * oldMinGas + hgu.MinGas = int64(regressionFactor*float64(hgu.MovingAverageGas) + (1.0-regressionFactor)*float64(hgu.MinGas)) + hgu.BlockNum++ + if gk.gas > hgu.MaxGas { + hgu.MaxGas = gk.gas + } else if gk.gas < hgu.MinGas { + hgu.MinGas = gk.gas + } + // add to cache if hit + if cacheHit { + h.cache.Add(gk.key, hgu) + } + } + + data, err := proto.Marshal(hgu) + if err != nil { + log.Println("flushHgu marshal error:", err) + continue } - h.guDB.Set([]byte(gk.key), int64ToBytes(avgGas)) + + h.guDB.Set([]byte(gk.key), data) } } @@ -132,12 +163,10 @@ func initDb() db.DB { return db } -func int64ToBytes(i int64) []byte { - var buf = make([]byte, 8) - binary.BigEndian.PutUint64(buf, uint64(i)) - return buf -} - -func bytesToInt64(buf []byte) int64 { - return int64(binary.BigEndian.Uint64(buf)) +func meanGas(allGas []int64) int64 { + var totalGas int64 + for _, gas := range allGas { + totalGas += gas + } + return totalGas / int64(len(allGas)) } diff --git a/libs/cosmos-sdk/baseapp/gasuseddb_test.go b/libs/cosmos-sdk/baseapp/gasuseddb_test.go new file mode 100644 index 0000000000..7383c53c13 --- /dev/null +++ b/libs/cosmos-sdk/baseapp/gasuseddb_test.go @@ -0,0 +1,118 @@ +package baseapp + +import ( + "github.com/stretchr/testify/require" + "os" + "testing" + "time" +) + +type hguTestCase struct { + key string + gasUsed int64 +} + +func TestHGU(t *testing.T) { + t.Cleanup(func() { + os.RemoveAll(HistoryGasUsedDbDir) + }) + testCase := []hguTestCase{ + {"test0", 1}, + {"test0", 2}, + {"test0", 3}, + {"test0", 4}, + {"test0", 5}, + {"test1", 10}, + {"test2", 20}, + } + expected := []struct { + key string + record HguRecord + }{ + { + "test0", + HguRecord{ + MaxGas: 3, + MinGas: 3, + MovingAverageGas: 3, + }, + }, + { + "test1", + HguRecord{ + MaxGas: 10, + MinGas: 10, + MovingAverageGas: 10, + }, + }, + { + "test2", + HguRecord{ + MaxGas: 20, + MinGas: 20, + MovingAverageGas: 20, + }, + }, + } + hguDB := InstanceOfHistoryGasUsedRecordDB() + for _, c := range testCase { + hguDB.UpdateGasUsed([]byte(c.key), c.gasUsed) + } + for _, c := range expected { + r, ok := hguDB.getHgu([]byte(c.key)) + require.False(t, ok) + require.Nil(t, r) + } + + hguDB.FlushHgu() + time.Sleep(time.Second) + for _, c := range expected { + r, ok := hguDB.getHgu([]byte(c.key)) + require.False(t, ok) + require.Equal(t, c.record, *r) + } + + for _, c := range expected { + r := hguDB.GetHgu([]byte(c.key)) + require.Equal(t, c.record, *r) + } + + for _, c := range expected { + r, ok := hguDB.getHgu([]byte(c.key)) + require.True(t, ok) + require.Equal(t, c.record, *r) + } + + hguDB.UpdateGasUsed([]byte("test0"), 1) + hguDB.FlushHgu() + hguDB.UpdateGasUsed([]byte("test0"), 10) + hguDB.FlushHgu() + time.Sleep(time.Second) + + r, ok := hguDB.getHgu([]byte("test0")) + require.True(t, ok) + require.Equal(t, int64(10), r.MaxGas) + require.Equal(t, int64(1), r.MinGas) + require.Equal(t, int64(5), r.MovingAverageGas) + require.Equal(t, int64(2), r.BlockNum) +} + +func TestMovingAverageGas(t *testing.T) { + t.Cleanup(func() { + os.RemoveAll(HistoryGasUsedDbDir) + }) + testKey := []byte("test") + cases := []int64{21000, 23000, 25000, 33000, 37000, 53000} + hguDB := InstanceOfHistoryGasUsedRecordDB() + for _, gas := range cases { + hguDB.UpdateGasUsed(testKey, gas) + hguDB.FlushHgu() + } + time.Sleep(time.Second) + + r := hguDB.GetHgu(testKey) + require.Equal(t, int64(53000), r.MaxGas) + require.Equal(t, int64(22811), r.MinGas) + require.Equal(t, int64(39816), r.MovingAverageGas) + require.Equal(t, int64(5), r.BlockNum) +} diff --git a/libs/cosmos-sdk/baseapp/hgu.pb.go b/libs/cosmos-sdk/baseapp/hgu.pb.go new file mode 100644 index 0000000000..612cdf636c --- /dev/null +++ b/libs/cosmos-sdk/baseapp/hgu.pb.go @@ -0,0 +1,102 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: hgu.proto + +package baseapp + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type HguRecord struct { + MaxGas int64 `protobuf:"varint,1,opt,name=MaxGas,proto3" json:"MaxGas,omitempty"` + MinGas int64 `protobuf:"varint,2,opt,name=MinGas,proto3" json:"MinGas,omitempty"` + MovingAverageGas int64 `protobuf:"varint,3,opt,name=MovingAverageGas,proto3" json:"MovingAverageGas,omitempty"` + BlockNum int64 `protobuf:"varint,4,opt,name=BlockNum,proto3" json:"BlockNum,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HguRecord) Reset() { *m = HguRecord{} } +func (m *HguRecord) String() string { return proto.CompactTextString(m) } +func (*HguRecord) ProtoMessage() {} +func (*HguRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_e71af30b0c14e8b0, []int{0} +} +func (m *HguRecord) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HguRecord.Unmarshal(m, b) +} +func (m *HguRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HguRecord.Marshal(b, m, deterministic) +} +func (m *HguRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_HguRecord.Merge(m, src) +} +func (m *HguRecord) XXX_Size() int { + return xxx_messageInfo_HguRecord.Size(m) +} +func (m *HguRecord) XXX_DiscardUnknown() { + xxx_messageInfo_HguRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_HguRecord proto.InternalMessageInfo + +func (m *HguRecord) GetMaxGas() int64 { + if m != nil { + return m.MaxGas + } + return 0 +} + +func (m *HguRecord) GetMinGas() int64 { + if m != nil { + return m.MinGas + } + return 0 +} + +func (m *HguRecord) GetMovingAverageGas() int64 { + if m != nil { + return m.MovingAverageGas + } + return 0 +} + +func (m *HguRecord) GetBlockNum() int64 { + if m != nil { + return m.BlockNum + } + return 0 +} + +func init() { + proto.RegisterType((*HguRecord)(nil), "HguRecord") +} + +func init() { proto.RegisterFile("hgu.proto", fileDescriptor_e71af30b0c14e8b0) } + +var fileDescriptor_e71af30b0c14e8b0 = []byte{ + // 136 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcc, 0x48, 0x2f, 0xd5, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x6a, 0x66, 0xe4, 0xe2, 0xf4, 0x48, 0x2f, 0x0d, 0x4a, 0x4d, + 0xce, 0x2f, 0x4a, 0x11, 0x12, 0xe3, 0x62, 0xf3, 0x4d, 0xac, 0x70, 0x4f, 0x2c, 0x96, 0x60, 0x54, + 0x60, 0xd4, 0x60, 0x0e, 0x82, 0xf2, 0xc0, 0xe2, 0x99, 0x79, 0x20, 0x71, 0x26, 0xa8, 0x38, 0x98, + 0x27, 0xa4, 0xc5, 0x25, 0xe0, 0x9b, 0x5f, 0x96, 0x99, 0x97, 0xee, 0x58, 0x96, 0x5a, 0x94, 0x98, + 0x9e, 0x0a, 0x52, 0xc1, 0x0c, 0x56, 0x81, 0x21, 0x2e, 0x24, 0xc5, 0xc5, 0xe1, 0x94, 0x93, 0x9f, + 0x9c, 0xed, 0x57, 0x9a, 0x2b, 0xc1, 0x02, 0x56, 0x03, 0xe7, 0x3b, 0x71, 0x46, 0xb1, 0x27, 0x25, + 0x16, 0xa7, 0x26, 0x16, 0x14, 0x24, 0xb1, 0x81, 0xdd, 0x65, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, + 0x82, 0xd0, 0xc9, 0x2f, 0xa4, 0x00, 0x00, 0x00, +} diff --git a/libs/cosmos-sdk/baseapp/proto/hgu.proto b/libs/cosmos-sdk/baseapp/proto/hgu.proto new file mode 100644 index 0000000000..f9d9d933e0 --- /dev/null +++ b/libs/cosmos-sdk/baseapp/proto/hgu.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +option go_package = "baseapp"; + +message HguRecord { + int64 MaxGas = 1; + int64 MinGas = 2; + int64 MovingAverageGas = 3; + int64 BlockNum = 4; +} diff --git a/libs/tendermint/cmd/tendermint/commands/run_node.go b/libs/tendermint/cmd/tendermint/commands/run_node.go index 365dfdbba5..a576e84763 100644 --- a/libs/tendermint/cmd/tendermint/commands/run_node.go +++ b/libs/tendermint/cmd/tendermint/commands/run_node.go @@ -136,11 +136,25 @@ func AddNodeFlags(cmd *cobra.Command) { false, "enable precise gas used", ) + cmd.Flags().Int64( + "mempool.pgu-percentage-threshold", + 10, + "use pgu when hgu has a margin of at least threshold percent", + ) + cmd.Flags().Int( + "mempool.pgu-concurrency", + 1, + "pgu concurrency", + ) cmd.Flags().Float64( "mempool.pgu-adjustment", 1, "adjustment for pgu, such as 0.9 or 1.1", ) + cmd.Flags().Bool( + "mempool.pgu-persist", + false, + "persist the gas estimated by pgu") cmd.Flags().Bool( "mempool.sort_tx_by_gp", config.Mempool.SortTxByGp, diff --git a/libs/tendermint/config/dynamic_config_okchain.go b/libs/tendermint/config/dynamic_config_okchain.go index 7f33d0cb2b..a7ae0d9da7 100644 --- a/libs/tendermint/config/dynamic_config_okchain.go +++ b/libs/tendermint/config/dynamic_config_okchain.go @@ -11,7 +11,10 @@ type IDynamicConfig interface { GetEnableDeleteMinGPTx() bool GetMaxGasUsedPerBlock() int64 GetEnablePGU() bool + GetPGUPercentageThreshold() int64 + GetPGUConcurrency() int GetPGUAdjustment() float64 + GetPGUPersist() bool GetMempoolFlush() bool GetNodeKeyWhitelist() []string GetMempoolCheckTxCost() bool @@ -81,10 +84,22 @@ func (d MockDynamicConfig) GetEnablePGU() bool { return false } +func (d MockDynamicConfig) GetPGUPercentageThreshold() int64 { + return 10 +} + +func (d MockDynamicConfig) GetPGUConcurrency() int { + return 1 +} + func (d MockDynamicConfig) GetPGUAdjustment() float64 { return 1 } +func (d MockDynamicConfig) GetPGUPersist() bool { + return false +} + func (d MockDynamicConfig) GetMempoolFlush() bool { return false } diff --git a/libs/tendermint/global/commit_mutex.go b/libs/tendermint/global/commit_mutex.go new file mode 100644 index 0000000000..b93d0b5d4a --- /dev/null +++ b/libs/tendermint/global/commit_mutex.go @@ -0,0 +1,30 @@ +package global + +import ( + "sync" +) + +var ( + signalMtx sync.RWMutex + commitSignal = make(chan struct{}) +) + +func init() { + close(commitSignal) +} + +func CommitLock() { + signalMtx.Lock() + commitSignal = make(chan struct{}) + signalMtx.Unlock() +} + +func CommitUnlock() { + close(commitSignal) +} + +func WaitCommit() { + signalMtx.RLock() + <-commitSignal + signalMtx.RUnlock() +} diff --git a/libs/tendermint/mempool/clist_mempool.go b/libs/tendermint/mempool/clist_mempool.go index 2cd554cecb..291928ef75 100644 --- a/libs/tendermint/mempool/clist_mempool.go +++ b/libs/tendermint/mempool/clist_mempool.go @@ -12,23 +12,21 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - - lru "github.com/hashicorp/golang-lru" - "github.com/tendermint/go-amino" - "github.com/okex/exchain/libs/system/trace" abci "github.com/okex/exchain/libs/tendermint/abci/types" cfg "github.com/okex/exchain/libs/tendermint/config" + "github.com/okex/exchain/libs/tendermint/global" "github.com/okex/exchain/libs/tendermint/libs/clist" "github.com/okex/exchain/libs/tendermint/libs/log" tmmath "github.com/okex/exchain/libs/tendermint/libs/math" "github.com/okex/exchain/libs/tendermint/proxy" "github.com/okex/exchain/libs/tendermint/types" + "github.com/tendermint/go-amino" ) type TxInfoParser interface { GetRawTxInfo(tx types.Tx) ExTxInfo - GetTxHistoryGasUsed(tx types.Tx) int64 + GetTxHistoryGasUsed(tx types.Tx, gasLimit int64) (int64, bool) GetRealTxFromRawTx(rawTx types.Tx) abci.TxEssentials } @@ -78,7 +76,8 @@ type CListMempool struct { eventBus types.TxEventPublisher - logger log.Logger + logger log.Logger + pguLogger log.Logger metrics *Metrics @@ -100,10 +99,7 @@ type CListMempool struct { txs ITransactionQueue - simQueue chan *mempoolTx - - gasCache *lru.Cache - + simQueue chan *mempoolTx rmPendingTxChan chan types.EventDataRmPendingTx gpo *Oracle @@ -140,12 +136,9 @@ func NewCListMempool( txQueue = NewBaseTxQueue() } - gasCache, err := lru.New(1000000) - if err != nil { - panic(err) - } gpoConfig := NewGPOConfig(cfg.DynamicConfig.GetDynamicGpWeight(), cfg.DynamicConfig.GetDynamicGpCheckBlocks()) gpo := NewOracle(gpoConfig) + mempool := &CListMempool{ config: config, proxyAppConn: proxyAppConn, @@ -154,10 +147,10 @@ func NewCListMempool( recheckEnd: nil, eventBus: types.NopEventBus{}, logger: log.NewNopLogger(), + pguLogger: log.NewNopLogger(), metrics: NopMetrics(), txs: txQueue, - simQueue: make(chan *mempoolTx, 100000), - gasCache: gasCache, + simQueue: make(chan *mempoolTx, 200000), gpo: gpo, } @@ -165,7 +158,10 @@ func NewCListMempool( mempool.rmPendingTxChan = make(chan types.EventDataRmPendingTx, 1000) go mempool.fireRmPendingTxEvents() } - go mempool.simulationRoutine() + + for i := 0; i < cfg.DynamicConfig.GetPGUConcurrency(); i++ { + go mempool.simulationRoutine() + } if cfg.DynamicConfig.GetMempoolCacheSize() > 0 { mempool.cache = newMapTxCache(cfg.DynamicConfig.GetMempoolCacheSize()) @@ -205,6 +201,7 @@ func (mem *CListMempool) SetEventBus(eventBus types.TxEventPublisher) { // SetLogger sets the Logger. func (mem *CListMempool) SetLogger(l log.Logger) { mem.logger = l + mem.pguLogger = l.With("module", "pgu") } // WithPreCheck sets a filter for the mempool to reject a tx if f(tx) returns @@ -356,9 +353,6 @@ func (mem *CListMempool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo Tx defer mem.updateMtx.RUnlock() var err error - if cfg.DynamicConfig.GetMaxGasUsedPerBlock() > -1 { - txInfo.gasUsed = mem.txInfoparser.GetTxHistoryGasUsed(tx) - } if mem.preCheck != nil { if err = mem.preCheck(tx); err != nil { @@ -374,14 +368,18 @@ func (mem *CListMempool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo Tx if txInfo.from != "" { types.SignatureCache().Add(txkey[:], txInfo.from) } + reqRes := mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{Tx: tx, Type: txInfo.checkType, From: txInfo.wtx.GetFrom(), Nonce: nonce}) if r, ok := reqRes.Response.Value.(*abci.Response_CheckTx); ok { - if txInfo.gasUsed <= 0 || txInfo.gasUsed > r.CheckTx.GasWanted { - txInfo.gasUsed = r.CheckTx.GasWanted - } + gasLimit := r.CheckTx.GasWanted if cfg.DynamicConfig.GetMaxGasUsedPerBlock() > -1 { + txHash := tx.Hash(mem.Height()) + txInfo.gasUsed, txInfo.isGasPrecise = mem.txInfoparser.GetTxHistoryGasUsed(tx, gasLimit) // r.CheckTx.GasWanted is gasLimit mem.logger.Info(fmt.Sprintf("mempool.SimulateTx: txhash<%s>, gasLimit<%d>, gasUsed<%d>", - hex.EncodeToString(tx.Hash(mem.Height())), r.CheckTx.GasWanted, txInfo.gasUsed)) + hex.EncodeToString(txHash), r.CheckTx.GasWanted, txInfo.gasUsed)) + } + if txInfo.gasUsed <= 0 || txInfo.gasUsed > gasLimit { + txInfo.gasUsed = gasLimit } } @@ -478,7 +476,7 @@ func (mem *CListMempool) addTx(memTx *mempoolTx) error { if err := mem.txs.Insert(memTx); err != nil { return err } - if cfg.DynamicConfig.GetMaxGasUsedPerBlock() > -1 && cfg.DynamicConfig.GetEnablePGU() { + if cfg.DynamicConfig.GetMaxGasUsedPerBlock() > -1 && cfg.DynamicConfig.GetEnablePGU() && atomic.LoadUint32(&memTx.isSim) == 0 { select { case mem.simQueue <- memTx: default: @@ -715,6 +713,11 @@ func (mem *CListMempool) resCbFirstTime( from: r.CheckTx.Tx.GetEthAddr(), senderNonce: r.CheckTx.SenderNonce, } + if txInfo.isGasPrecise { + // gas for hgu is precise, just mark it simulated, so it will not be simulated again + memTx.isSim = 1 + memTx.hguPrecise = true + } if txInfo.wrapCMTx != nil { memTx.isWrapCMTx = true @@ -833,12 +836,7 @@ func (mem *CListMempool) notifyTxsAvailable() { } func (mem *CListMempool) GetTxSimulateGas(txHash string) int64 { - hash := hex.EncodeToString([]byte(txHash)) - v, ok := mem.gasCache.Get(hash) - if !ok { - return -1 - } - return v.(int64) + return getPGUGas([]byte(txHash)) } func (mem *CListMempool) ReapEssentialTx(tx types.Tx) abci.TxEssentials { @@ -889,6 +887,7 @@ func (mem *CListMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) []types.Tx { // If maxGas is negative, skip this check. // Since newTotalGas < masGas, which // must be non-negative, it follows that this won't overflow. + atomic.AddUint32(&memTx.outdated, 1) gasWanted := atomic.LoadInt64(&memTx.gasWanted) newTotalGas := totalGas + gasWanted if maxGas > -1 && gasWanted >= maxGas { @@ -1028,7 +1027,7 @@ func (mem *CListMempool) Update( gasUsedPerTx := deliverTxResponses[i].GasUsed gasPricePerTx := big.NewInt(0) if ele := mem.cleanTx(height, tx, txCode); ele != nil { - atomic.AddUint32(&(ele.Value.(*mempoolTx).isOutdated), 1) + atomic.AddUint32(&(ele.Value.(*mempoolTx).outdated), 1) addr = ele.Address nonce = ele.Nonce gasPricePerTx = ele.GasPrice @@ -1263,8 +1262,11 @@ type mempoolTx struct { from string senderNonce uint64 - isOutdated uint32 - isSim uint32 + outdated uint32 + isSim uint32 + + // `hguPrecise` is true means hgu for this tx is precise and simulation is not necessary + hguPrecise bool isWrapCMTx bool wrapCMNonce uint64 @@ -1290,7 +1292,7 @@ func (memTx *mempoolTx) ToWrappedMempoolTx() types.WrappedMempoolTx { Signature: memTx.signature, From: memTx.from, SenderNonce: memTx.senderNonce, - Outdated: memTx.isOutdated, + Outdated: memTx.outdated, IsSim: memTx.isSim, IsWrapCMTx: memTx.isWrapCMTx, WrapCMNonce: memTx.wrapCMNonce, @@ -1442,17 +1444,29 @@ func (mem *CListMempool) consumePendingTxQueueJob() { } } -func (mem *CListMempool) simulateTx(tx types.Tx) (*SimulationResponse, error) { +func (mem *CListMempool) simulateTx(tx types.Tx, gasLimit int64) (int64, error) { var simuRes SimulationResponse res, err := mem.proxyAppConn.QuerySync(abci.RequestQuery{ Path: "app/simulate/mempool", Data: tx, }) if err != nil { - return nil, err + return 0, err } err = cdc.UnmarshalBinaryBare(res.Value, &simuRes) - return &simuRes, err + if err != nil { + return 0, err + } + gas := int64(simuRes.GasUsed) * int64(cfg.DynamicConfig.GetPGUAdjustment()*100) / 100 + mem.pguLogger.Info("simulateTx", "txHash", hex.EncodeToString(tx.Hash(mem.Height())), "gas", gas, "gasLimit", gasLimit) + if gas > gasLimit { + gas = gasLimit + } + txHash := tx.Hash(mem.Height()) + if err = updatePGU(txHash, gas); err != nil { + mem.logger.Error("updatePGU", "txHash", hex.EncodeToString(tx.Hash(mem.Height())), "simGas", gas, "error", err) + } + return gas, err } func (mem *CListMempool) simulationRoutine() { @@ -1463,21 +1477,62 @@ func (mem *CListMempool) simulationRoutine() { func (mem *CListMempool) simulationJob(memTx *mempoolTx) { defer types.SignatureCache().Remove(memTx.realTx.TxHash()) - if atomic.LoadUint32(&memTx.isOutdated) != 0 { + if atomic.LoadUint32(&memTx.outdated) != 0 { // memTx is outdated return } - simuRes, err := mem.simulateTx(memTx.tx) + global.WaitCommit() + gas, err := mem.simulateTx(memTx.tx, memTx.gasLimit) if err != nil { mem.logger.Error("simulateTx", "error", err, "txHash", memTx.tx.Hash(mem.Height())) return } - gas := int64(simuRes.GasUsed) * int64(cfg.DynamicConfig.GetPGUAdjustment()*100) / 100 - if gas < atomic.LoadInt64(&memTx.gasLimit) { - atomic.StoreInt64(&memTx.gasWanted, gas) - } + atomic.StoreInt64(&memTx.gasWanted, gas) atomic.AddUint32(&memTx.isSim, 1) - mem.gasCache.Add(hex.EncodeToString(memTx.realTx.TxHash()), gas) +} + +// trySimulate4BlockAfterNext will be called during Update() +// assume that next step is to proposal a block of height `n` through ReapMaxBytesMaxGas +// trySimulate4NextBlock will skip those txs which would be packed into that block, +// and simulate txs to be packed into block of height `n+1` +func (mem *CListMempool) trySimulate4NextBlock() { + maxGu := cfg.DynamicConfig.GetMaxGasUsedPerBlock() + if maxGu < 0 || !cfg.DynamicConfig.GetEnablePGU() { + return + } + + var gu int64 + var ele *clist.CElement + // skip the txs that will be packed into next block + for ele = mem.txs.Front(); ele != nil; ele = ele.Next() { + gu += ele.Value.(*mempoolTx).gasWanted + if gu > maxGu { + break + } + } + + // reset gu for next cycle + gu = 0 + + for ; ele != nil && gu < maxGu; ele = ele.Next() { + memTx := ele.Value.(*mempoolTx) + var gas int64 + var err error + if !memTx.hguPrecise { + gas, err = mem.simulateTx(memTx.tx, memTx.gasLimit) + if err != nil { + mem.logger.Error("trySimulate4BlockAfterNext", "error", err, "txHash", memTx.tx.Hash(mem.Height())) + return + } + atomic.StoreInt64(&memTx.gasWanted, gas) + atomic.AddUint32(&memTx.isSim, 1) + } else { + gas = memTx.gasWanted + } + + gu += gas + } + } func (mem *CListMempool) deleteMinGPTxOnlyFull() { diff --git a/libs/tendermint/mempool/clist_mempool_test.go b/libs/tendermint/mempool/clist_mempool_test.go index b89fff9e01..b334bcedba 100644 --- a/libs/tendermint/mempool/clist_mempool_test.go +++ b/libs/tendermint/mempool/clist_mempool_test.go @@ -105,10 +105,11 @@ func TestReapMaxBytesMaxGas(t *testing.T) { tx0 := mempool.TxsFront().Value.(*mempoolTx) // assert that kv store has gas wanted = 1. require.Equal(t, app.CheckTx(abci.RequestCheckTx{Tx: tx0.tx}).GasWanted, int64(1), "KVStore had a gas value neq to 1") - require.Equal(t, tx0.gasWanted, int64(1), "transactions gas was set incorrectly") + require.Equal(t, tx0.gasLimit, int64(1), "transactions gas was set incorrectly") // ensure each tx is 20 bytes long require.Equal(t, len(tx0.tx), 20, "Tx is longer than 20 bytes") mempool.Flush() + assert.Zero(t, mempool.Size()) // each table driven test creates numTxsToCreate txs with checkTx, and at the end clears all remaining txs. // each tx has 20 bytes + amino overhead = 21 bytes, 1 gas @@ -141,6 +142,7 @@ func TestReapMaxBytesMaxGas(t *testing.T) { assert.Equal(t, tt.expectedNumTxs, len(got), "Got %d txs, expected %d, tc #%d", len(got), tt.expectedNumTxs, tcIndex) mempool.Flush() + assert.Zero(t, mempool.Size()) } } diff --git a/libs/tendermint/mempool/mempool.go b/libs/tendermint/mempool/mempool.go index ba0b8fc6cb..53d41feda3 100644 --- a/libs/tendermint/mempool/mempool.go +++ b/libs/tendermint/mempool/mempool.go @@ -118,11 +118,11 @@ type TxInfo struct { // SenderP2PID is the actual p2p.ID of the sender, used e.g. for logging. SenderP2PID p2p.ID - from string - wtx *WrappedTx - checkType abci.CheckTxType - - gasUsed int64 + from string + wtx *WrappedTx + checkType abci.CheckTxType + isGasPrecise bool + gasUsed int64 wrapCMTx *types.WrapCMTx } diff --git a/libs/tendermint/mempool/pgu.go b/libs/tendermint/mempool/pgu.go new file mode 100644 index 0000000000..cc7cdc3a73 --- /dev/null +++ b/libs/tendermint/mempool/pgu.go @@ -0,0 +1,52 @@ +package mempool + +import ( + "encoding/binary" + cfg "github.com/okex/exchain/libs/tendermint/config" + "path/filepath" + "sync" + + "github.com/okex/exchain/libs/cosmos-sdk/client/flags" + sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + db "github.com/okex/exchain/libs/tm-db" + "github.com/spf13/viper" +) + +const ( + pguDBDir = "data" + pguDBName = "pgu" +) + +var ( + pguDB db.DB + pguOnce sync.Once +) + +func initDB() { + homeDir := viper.GetString(flags.FlagHome) + dbPath := filepath.Join(homeDir, pguDBDir) + var err error + pguDB, err = sdk.NewDB(pguDBName, dbPath) + if err != nil { + panic(err) + } +} + +func updatePGU(txHash []byte, gas int64) error { + if !cfg.DynamicConfig.GetPGUPersist() { + return nil + } + pguOnce.Do(initDB) + bytesGas := make([]byte, 8) + binary.BigEndian.PutUint64(bytesGas, uint64(gas)) + return pguDB.Set(txHash, bytesGas) +} + +func getPGUGas(txHash []byte) int64 { + pguOnce.Do(initDB) + data, err := pguDB.Get(txHash) + if err != nil || len(data) == 0 { + return -1 + } + return int64(binary.BigEndian.Uint64(data)) +} diff --git a/libs/tendermint/state/execution.go b/libs/tendermint/state/execution.go index 3cac27ace6..808601bf58 100644 --- a/libs/tendermint/state/execution.go +++ b/libs/tendermint/state/execution.go @@ -235,6 +235,10 @@ func (blockExec *BlockExecutor) ApplyBlock( deltaInfo := dc.prepareStateDelta(block.Height) trc.Pin(trace.Abci) + if cfg.DynamicConfig.GetEnablePGU() { + global.CommitLock() + defer global.CommitUnlock() + } startTime := time.Now().UnixNano() From 8f115ea85fd5e65770a824fd99deb8ff6ec8566e Mon Sep 17 00:00:00 2001 From: KamiD <44460798+KamiD@users.noreply.github.com> Date: Thu, 20 Jul 2023 11:28:51 +0800 Subject: [PATCH 7/9] Bump version to v1.7.9 (#3217) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e4bd0ff76a..42ae24bcbc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ IGNORE_CHECK_GO=false install_rocksdb_version:=$(ROCKSDB_VERSION) -Version=v1.7.8 +Version=v1.7.9 CosmosSDK=v0.39.2 Tendermint=v0.33.9 Iavl=v0.14.3 From 5d6720abb890f4886328fd85429e5bda8e343fb7 Mon Sep 17 00:00:00 2001 From: JianGuo Date: Thu, 20 Jul 2023 11:51:02 +0800 Subject: [PATCH 8/9] upgrade wasmvm from v1.0.0 to v1.0.1 (#3215) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6e47eddf8a..141a31784d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/99designs/keyring v1.1.6 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d - github.com/CosmWasm/wasmvm v1.0.0 + github.com/CosmWasm/wasmvm v1.0.1 github.com/VictoriaMetrics/fastcache v1.8.0 github.com/Workiva/go-datastructures v1.0.53 github.com/alicebob/miniredis/v2 v2.17.0 diff --git a/go.sum b/go.sum index ee866ca067..aa5fb45337 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmvm v1.0.0 h1:NRmnHe3xXsKn2uEcB1F5Ha323JVAhON+BI6L177dlKc= -github.com/CosmWasm/wasmvm v1.0.0/go.mod h1:ei0xpvomwSdONsxDuONzV7bL1jSET1M8brEx0FCXc+A= +github.com/CosmWasm/wasmvm v1.0.1 h1:ZgkPm/nMiahE2CNjQRsOJTjF3eNkjeDVVWt3Pf7B0Gc= +github.com/CosmWasm/wasmvm v1.0.1/go.mod h1:ei0xpvomwSdONsxDuONzV7bL1jSET1M8brEx0FCXc+A= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= From f4383191217607b47fe1cc2ddb056b95cdad05bb Mon Sep 17 00:00:00 2001 From: Leo <52782564+LeoGuo621@users.noreply.github.com> Date: Mon, 24 Jul 2023 09:53:57 +0800 Subject: [PATCH 9/9] optimize tx parallel execution ut (#3203) * optimize ut * Update app_parallel_test.go modify code * inrich ut and fix bug of it * Update app_parallel_test.go fix bug * optimize ut * Update app_parallel_test.go enrich ut * support vmb tx * optimize ut * Update app_parallel_test.go remove useless code * Update app_parallel_test.go modify ut --- app/app_parallel_test.go | 1218 +++++++++++++++++ app/app_test.go | 31 +- app/test_helpers.go | 43 + app/testdata/cw20.wasm | Bin 0 -> 171387 bytes app/testdata/freecall.sol | 40 + app/testdata/freecall.wasm | Bin 0 -> 188436 bytes app/testdata/precompile.sol | 73 + app/testdata/precompile.wasm | Bin 0 -> 188738 bytes app/testdata/testerc20.sol | 234 ++++ .../baseapp/baseapp_paralled_test.go | 352 ----- libs/tendermint/types/milestone.go | 4 + 11 files changed, 1625 insertions(+), 370 deletions(-) create mode 100644 app/app_parallel_test.go create mode 100644 app/testdata/cw20.wasm create mode 100644 app/testdata/freecall.sol create mode 100644 app/testdata/freecall.wasm create mode 100644 app/testdata/precompile.sol create mode 100644 app/testdata/precompile.wasm create mode 100644 app/testdata/testerc20.sol delete mode 100644 libs/cosmos-sdk/baseapp/baseapp_paralled_test.go diff --git a/app/app_parallel_test.go b/app/app_parallel_test.go new file mode 100644 index 0000000000..f207579c90 --- /dev/null +++ b/app/app_parallel_test.go @@ -0,0 +1,1218 @@ +package app_test + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "reflect" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + ethcmn "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + + "github.com/okex/exchain/app" + "github.com/okex/exchain/app/crypto/ethsecp256k1" + apptypes "github.com/okex/exchain/app/types" + "github.com/okex/exchain/libs/cosmos-sdk/codec" + "github.com/okex/exchain/libs/cosmos-sdk/simapp/helpers" + sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/cosmos-sdk/x/auth" + authexported "github.com/okex/exchain/libs/cosmos-sdk/x/auth/exported" + abci "github.com/okex/exchain/libs/tendermint/abci/types" + tmtypes "github.com/okex/exchain/libs/tendermint/types" + evmtypes "github.com/okex/exchain/x/evm/types" + tokentypes "github.com/okex/exchain/x/token/types" + wasmtypes "github.com/okex/exchain/x/wasm/types" +) + +var ( + testPrecompileCodeA = "60806040526101006000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005257600080fd5b50610b76806100626000396000f3fe60806040526004361061004a5760003560e01c80635b3082c21461004f57806363de1b5d1461007f5780636bbb9b13146100af5780638381f58a146100df578063be2b0ac21461010a575b600080fd5b610069600480360381019061006491906106cc565b610147565b60405161007691906108ba565b60405180910390f35b61009960048036038101906100949190610670565b610161565b6040516100a69190610898565b60405180910390f35b6100c960048036038101906100c49190610744565b610314565b6040516100d69190610898565b60405180910390f35b3480156100eb57600080fd5b506100f46104ca565b6040516101019190610913565b60405180910390f35b34801561011657600080fd5b50610131600480360381019061012c91906105de565b6104d0565b60405161013e91906108ba565b60405180910390f35b606060405180602001604052806000815250905092915050565b60606001805461017191906109c7565b60018190555060008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1634866040516024016101c391906108ba565b6040516020818303038152906040527fbe2b0ac2000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161024d9190610881565b60006040518083038185875af1925050503d806000811461028a576040519150601f19603f3d011682016040523d82523d6000602084013e61028f565b606091505b509150915083156102f557816102a457600080fd5b6000818060200190518101906102ba9190610627565b90507fe390e3d6b4766bc311796e6b5ce75dd6d51f0cb55cea58be963a5e7972ade65c816040516102eb91906108ba565b60405180910390a1505b6001805461030391906109c7565b600181905550809250505092915050565b60606001805461032491906109c7565b60018190555060008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163487876040516024016103789291906108dc565b6040516020818303038152906040527f5b3082c2000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516104029190610881565b60006040518083038185875af1925050503d806000811461043f576040519150601f19603f3d011682016040523d82523d6000602084013e610444565b606091505b509150915083156104aa578161045957600080fd5b60008180602001905181019061046f9190610627565b90507fe390e3d6b4766bc311796e6b5ce75dd6d51f0cb55cea58be963a5e7972ade65c816040516104a091906108ba565b60405180910390a1505b600180546104b891906109c7565b60018190555080925050509392505050565b60015481565b6060604051806020016040528060008152509050919050565b60006104fc6104f784610953565b61092e565b90508281526020810184848401111561051857610517610b09565b5b610523848285610a33565b509392505050565b600061053e61053984610953565b61092e565b90508281526020810184848401111561055a57610559610b09565b5b610565848285610a42565b509392505050565b60008135905061057c81610b29565b92915050565b600082601f83011261059757610596610b04565b5b81356105a78482602086016104e9565b91505092915050565b600082601f8301126105c5576105c4610b04565b5b81516105d584826020860161052b565b91505092915050565b6000602082840312156105f4576105f3610b13565b5b600082013567ffffffffffffffff81111561061257610611610b0e565b5b61061e84828501610582565b91505092915050565b60006020828403121561063d5761063c610b13565b5b600082015167ffffffffffffffff81111561065b5761065a610b0e565b5b610667848285016105b0565b91505092915050565b6000806040838503121561068757610686610b13565b5b600083013567ffffffffffffffff8111156106a5576106a4610b0e565b5b6106b185828601610582565b92505060206106c28582860161056d565b9150509250929050565b600080604083850312156106e3576106e2610b13565b5b600083013567ffffffffffffffff81111561070157610700610b0e565b5b61070d85828601610582565b925050602083013567ffffffffffffffff81111561072e5761072d610b0e565b5b61073a85828601610582565b9150509250929050565b60008060006060848603121561075d5761075c610b13565b5b600084013567ffffffffffffffff81111561077b5761077a610b0e565b5b61078786828701610582565b935050602084013567ffffffffffffffff8111156107a8576107a7610b0e565b5b6107b486828701610582565b92505060406107c58682870161056d565b9150509250925092565b60006107da82610984565b6107e4818561099a565b93506107f4818560208601610a42565b6107fd81610b18565b840191505092915050565b600061081382610984565b61081d81856109ab565b935061082d818560208601610a42565b80840191505092915050565b60006108448261098f565b61084e81856109b6565b935061085e818560208601610a42565b61086781610b18565b840191505092915050565b61087b81610a29565b82525050565b600061088d8284610808565b915081905092915050565b600060208201905081810360008301526108b281846107cf565b905092915050565b600060208201905081810360008301526108d48184610839565b905092915050565b600060408201905081810360008301526108f68185610839565b9050818103602083015261090a8184610839565b90509392505050565b60006020820190506109286000830184610872565b92915050565b6000610938610949565b90506109448282610a75565b919050565b6000604051905090565b600067ffffffffffffffff82111561096e5761096d610ad5565b5b61097782610b18565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b60006109d282610a29565b91506109dd83610a29565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610a1257610a11610aa6565b5b828201905092915050565b60008115159050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015610a60578082015181840152602081019050610a45565b83811115610a6f576000848401525b50505050565b610a7e82610b18565b810181811067ffffffffffffffff82111715610a9d57610a9c610ad5565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b610b3281610a1d565b8114610b3d57600080fd5b5056fea264697066735822122099b3fbd7a2bf1822c7f366e7e6685aa6801d09d9932acbf59c0687cae6df69da64736f6c63430008070033" + + contractJson = `{"abi":[{"inputs":[],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"608060405234801561001057600080fd5b50610205806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632e64cec1146100465780634f2be91f146100645780636057361d1461006e575b600080fd5b61004e61008a565b60405161005b91906100d1565b60405180910390f35b61006c610093565b005b6100886004803603810190610083919061011d565b6100ae565b005b60008054905090565b60016000808282546100a59190610179565b92505081905550565b8060008190555050565b6000819050919050565b6100cb816100b8565b82525050565b60006020820190506100e660008301846100c2565b92915050565b600080fd5b6100fa816100b8565b811461010557600080fd5b50565b600081359050610117816100f1565b92915050565b600060208284031215610133576101326100ec565b5b600061014184828501610108565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610184826100b8565b915061018f836100b8565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101c4576101c361014a565b5b82820190509291505056fea2646970667358221220742b7232e733bee3592cb9e558bdae3fbd0006bcbdba76abc47b6020744037b364736f6c634300080a0033"}` + + testPrecompileABIAJson = "{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"wasmAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"data\",\"type\":\"string\"}],\"name\":\"callToWasm\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"response\",\"type\":\"string\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"wasmAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"msgData\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"requireASuccess\",\"type\":\"bool\"}],\"name\":\"callWasm\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"response\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"string\",\"name\":\"data\",\"type\":\"string\"}],\"name\":\"pushLog\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"msgData\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"requireASuccess\",\"type\":\"bool\"}],\"name\":\"queryWasm\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"response\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"number\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"data\",\"type\":\"string\"}],\"name\":\"queryToWasm\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"response\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"bin\":\"60806040526101006000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005257600080fd5b50610b76806100626000396000f3fe60806040526004361061004a5760003560e01c80635b3082c21461004f57806363de1b5d1461007f5780636bbb9b13146100af5780638381f58a146100df578063be2b0ac21461010a575b600080fd5b610069600480360381019061006491906106cc565b610147565b60405161007691906108ba565b60405180910390f35b61009960048036038101906100949190610670565b610161565b6040516100a69190610898565b60405180910390f35b6100c960048036038101906100c49190610744565b610314565b6040516100d69190610898565b60405180910390f35b3480156100eb57600080fd5b506100f46104ca565b6040516101019190610913565b60405180910390f35b34801561011657600080fd5b50610131600480360381019061012c91906105de565b6104d0565b60405161013e91906108ba565b60405180910390f35b606060405180602001604052806000815250905092915050565b60606001805461017191906109c7565b60018190555060008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1634866040516024016101c391906108ba565b6040516020818303038152906040527fbe2b0ac2000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161024d9190610881565b60006040518083038185875af1925050503d806000811461028a576040519150601f19603f3d011682016040523d82523d6000602084013e61028f565b606091505b509150915083156102f557816102a457600080fd5b6000818060200190518101906102ba9190610627565b90507fe390e3d6b4766bc311796e6b5ce75dd6d51f0cb55cea58be963a5e7972ade65c816040516102eb91906108ba565b60405180910390a1505b6001805461030391906109c7565b600181905550809250505092915050565b60606001805461032491906109c7565b60018190555060008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163487876040516024016103789291906108dc565b6040516020818303038152906040527f5b3082c2000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516104029190610881565b60006040518083038185875af1925050503d806000811461043f576040519150601f19603f3d011682016040523d82523d6000602084013e610444565b606091505b509150915083156104aa578161045957600080fd5b60008180602001905181019061046f9190610627565b90507fe390e3d6b4766bc311796e6b5ce75dd6d51f0cb55cea58be963a5e7972ade65c816040516104a091906108ba565b60405180910390a1505b600180546104b891906109c7565b60018190555080925050509392505050565b60015481565b6060604051806020016040528060008152509050919050565b60006104fc6104f784610953565b61092e565b90508281526020810184848401111561051857610517610b09565b5b610523848285610a33565b509392505050565b600061053e61053984610953565b61092e565b90508281526020810184848401111561055a57610559610b09565b5b610565848285610a42565b509392505050565b60008135905061057c81610b29565b92915050565b600082601f83011261059757610596610b04565b5b81356105a78482602086016104e9565b91505092915050565b600082601f8301126105c5576105c4610b04565b5b81516105d584826020860161052b565b91505092915050565b6000602082840312156105f4576105f3610b13565b5b600082013567ffffffffffffffff81111561061257610611610b0e565b5b61061e84828501610582565b91505092915050565b60006020828403121561063d5761063c610b13565b5b600082015167ffffffffffffffff81111561065b5761065a610b0e565b5b610667848285016105b0565b91505092915050565b6000806040838503121561068757610686610b13565b5b600083013567ffffffffffffffff8111156106a5576106a4610b0e565b5b6106b185828601610582565b92505060206106c28582860161056d565b9150509250929050565b600080604083850312156106e3576106e2610b13565b5b600083013567ffffffffffffffff81111561070157610700610b0e565b5b61070d85828601610582565b925050602083013567ffffffffffffffff81111561072e5761072d610b0e565b5b61073a85828601610582565b9150509250929050565b60008060006060848603121561075d5761075c610b13565b5b600084013567ffffffffffffffff81111561077b5761077a610b0e565b5b61078786828701610582565b935050602084013567ffffffffffffffff8111156107a8576107a7610b0e565b5b6107b486828701610582565b92505060406107c58682870161056d565b9150509250925092565b60006107da82610984565b6107e4818561099a565b93506107f4818560208601610a42565b6107fd81610b18565b840191505092915050565b600061081382610984565b61081d81856109ab565b935061082d818560208601610a42565b80840191505092915050565b60006108448261098f565b61084e81856109b6565b935061085e818560208601610a42565b61086781610b18565b840191505092915050565b61087b81610a29565b82525050565b600061088d8284610808565b915081905092915050565b600060208201905081810360008301526108b281846107cf565b905092915050565b600060208201905081810360008301526108d48184610839565b905092915050565b600060408201905081810360008301526108f68185610839565b9050818103602083015261090a8184610839565b90509392505050565b60006020820190506109286000830184610872565b92915050565b6000610938610949565b90506109448282610a75565b919050565b6000604051905090565b600067ffffffffffffffff82111561096e5761096d610ad5565b5b61097782610b18565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b60006109d282610a29565b91506109dd83610a29565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610a1257610a11610aa6565b5b828201905092915050565b60008115159050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015610a60578082015181840152602081019050610a45565b83811115610a6f576000848401525b50505050565b610a7e82610b18565b810181811067ffffffffffffffff82111715610a9d57610a9c610ad5565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b610b3281610a1d565b8114610b3d57600080fd5b5056fea264697066735822122099b3fbd7a2bf1822c7f366e7e6685aa6801d09d9932acbf59c0687cae6df69da64736f6c63430008070033\"}" + callWasmMsgFormat = "{\"transfer\":{\"amount\":\"%d\",\"recipient\":\"%s\"}}" +) + +type Env struct { + priv []ethsecp256k1.PrivKey + addr []sdk.AccAddress +} + +type Chain struct { + app *app.OKExChainApp + codec *codec.Codec + priv []ethsecp256k1.PrivKey + addr []sdk.AccAddress + acc []apptypes.EthAccount + seq []uint64 + num []uint64 + chainIdStr string + chainIdInt *big.Int + ContractAddr []byte + + erc20ABI abi.ABI + //vmb: evm->wasm + VMBContractA ethcmn.Address + VMBWasmContract sdk.WasmAddress + //vmb: wasm->evm + freeCallWasmContract sdk.WasmAddress + freeCallWasmCodeId uint64 + freeCallEvmContract ethcmn.Address + + timeYear int +} + +func NewChain(env *Env) *Chain { + chain := new(Chain) + chain.acc = make([]apptypes.EthAccount, 10) + chain.priv = make([]ethsecp256k1.PrivKey, 10) + chain.addr = make([]sdk.AccAddress, 10) + chain.seq = make([]uint64, 10) + chain.num = make([]uint64, 10) + chain.chainIdStr = "ethermint-3" + chain.chainIdInt = big.NewInt(3) + chain.timeYear = 2022 + // initialize account + genAccs := make([]authexported.GenesisAccount, 0) + for i := 0; i < 10; i++ { + chain.acc[i] = apptypes.EthAccount{ + BaseAccount: &auth.BaseAccount{ + Address: env.addr[i], + Coins: sdk.Coins{sdk.NewInt64Coin("okt", 1000000)}, + }, + CodeHash: ethcrypto.Keccak256(nil), + } + genAccs = append(genAccs, chain.acc[i]) + chain.priv[i] = env.priv[i] + chain.addr[i] = env.addr[i] + chain.seq[i] = 0 + chain.num[i] = uint64(i) + } + + chain.app = app.SetupWithGenesisAccounts(false, genAccs, app.WithChainId(chain.chainIdStr)) + chain.codec = chain.app.Codec() + + chain.app.WasmKeeper.SetParams(chain.Ctx(), wasmtypes.TestParams()) + params := evmtypes.DefaultParams() + params.EnableCreate = true + params.EnableCall = true + chain.app.EvmKeeper.SetParams(chain.Ctx(), params) + + chain.app.BaseApp.Commit(abci.RequestCommit{}) + return chain +} + +func (chain *Chain) Ctx() sdk.Context { + return chain.app.BaseApp.GetDeliverStateCtx() +} + +func DeployContractAndGetContractAddress(t *testing.T, chain *Chain) { + var rawTxs [][]byte + rawTxs = append(rawTxs, deployContract(t, chain, 0)) + r := runTxs(chain, rawTxs, false) + + log := r[0].Log[1 : len(r[0].Log)-1] + logMap := make(map[string]interface{}) + err := json.Unmarshal([]byte(log), &logMap) + require.NoError(t, err) + + logs := strings.Split(logMap["log"].(string), ";") + require.True(t, len(logs) == 3) + contractLog := strings.Split(logs[2], " ") + require.True(t, len(contractLog) == 4) + chain.ContractAddr = []byte(contractLog[3]) +} + +func createEthTx(t *testing.T, chain *Chain, addressIdx int) []byte { + amount, gasPrice, gasLimit := int64(1024), int64(2048), uint64(100000) + addrTo := ethcmn.BytesToAddress(chain.priv[addressIdx+1].PubKey().Address().Bytes()) + msg := evmtypes.NewMsgEthereumTx(chain.seq[addressIdx], &addrTo, big.NewInt(amount), gasLimit, big.NewInt(gasPrice), []byte{}) + chain.seq[addressIdx]++ + err := msg.Sign(chain.chainIdInt, chain.priv[addressIdx].ToECDSA()) + require.NoError(t, err) + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + + return rawTx +} + +func createAnteErrEthTx(t *testing.T, chain *Chain, addressIdx int) []byte { + amount, gasPrice, gasLimit := int64(1024), int64(2048), uint64(100000) + addrTo := ethcmn.BytesToAddress(chain.priv[addressIdx+1].PubKey().Address().Bytes()) + //Note: anteErr occur (invalid nonce) + msg := evmtypes.NewMsgEthereumTx(chain.seq[addressIdx]+1, &addrTo, big.NewInt(amount), gasLimit, big.NewInt(gasPrice), []byte{}) + err := msg.Sign(chain.chainIdInt, chain.priv[addressIdx].ToECDSA()) + require.NoError(t, err) + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + + return rawTx +} + +func createFailedEthTx(t *testing.T, chain *Chain, addressIdx int) []byte { + amount, gasPrice, gasLimit := int64(1024), int64(2048), uint64(1) + addrTo := ethcmn.BytesToAddress(chain.priv[addressIdx+1].PubKey().Address().Bytes()) + msg := evmtypes.NewMsgEthereumTx(chain.seq[addressIdx], &addrTo, big.NewInt(amount), gasLimit, big.NewInt(gasPrice), []byte{}) + chain.seq[addressIdx]++ + err := msg.Sign(chain.chainIdInt, chain.priv[addressIdx].ToECDSA()) + require.NoError(t, err) + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + + return rawTx +} + +func createTokenSendTx(t *testing.T, chain *Chain, i int) []byte { + msg := tokentypes.NewMsgTokenSend(chain.addr[i], chain.addr[i+1], sdk.Coins{sdk.NewInt64Coin("okt", 1)}) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, + helpers.DefaultGenTxGas, + chain.chainIdStr, + []uint64{chain.num[i]}, + []uint64{chain.seq[i]}, + chain.priv[i], + ) + chain.seq[i]++ + + txBytes, err := chain.app.Codec().MarshalBinaryLengthPrefixed(tx) + require.Nil(t, err) + return txBytes +} + +func createFailedTokenSendTx(t *testing.T, chain *Chain, i int) []byte { + msg := tokentypes.NewMsgTokenSend(chain.addr[i], chain.addr[i+1], sdk.Coins{sdk.NewInt64Coin("okt", 100000000000)}) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, + helpers.DefaultGenTxGas, + chain.chainIdStr, + []uint64{chain.num[i]}, + []uint64{chain.seq[i]}, + chain.priv[i], + ) + chain.seq[i]++ + + txBytes, err := chain.app.Codec().MarshalBinaryLengthPrefixed(tx) + require.Nil(t, err) + return txBytes +} + +func createAnteErrTokenSendTx(t *testing.T, chain *Chain, i int) []byte { + msg := tokentypes.NewMsgTokenSend(chain.addr[i], chain.addr[i+1], sdk.Coins{sdk.NewInt64Coin("okt", 1)}) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000000000)}, + helpers.DefaultGenTxGas, + chain.chainIdStr, + []uint64{chain.num[i]}, + []uint64{chain.seq[i]}, + chain.priv[i], + ) + + txBytes, err := chain.app.Codec().MarshalBinaryLengthPrefixed(tx) + require.Nil(t, err) + return txBytes +} + +func runTxs(chain *Chain, rawTxs [][]byte, isParallel bool) []*abci.ResponseDeliverTx { + timeValue := fmt.Sprintf("%d-04-11 13:33:37", chain.timeYear+1) + testTime, _ := time.Parse("2006-01-02 15:04:05", timeValue) + header := abci.Header{Height: chain.app.LastBlockHeight() + 1, ChainID: chain.chainIdStr, Time: testTime} + chain.app.BaseApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + var ret []*abci.ResponseDeliverTx + if isParallel { + ret = chain.app.BaseApp.ParallelTxs(rawTxs, false) + } else { + for _, tx := range rawTxs { + r := chain.app.BaseApp.DeliverTx(abci.RequestDeliverTx{Tx: tx}) + ret = append(ret, &r) + } + } + chain.app.BaseApp.EndBlock(abci.RequestEndBlock{}) + chain.app.BaseApp.Commit(abci.RequestCommit{}) + + return ret +} + +func TestParallelTxs(t *testing.T) { + + tmtypes.UnittestOnlySetMilestoneVenusHeight(-1) + tmtypes.UnittestOnlySetMilestoneVenus1Height(1) + tmtypes.UnittestOnlySetMilestoneVenus2Height(1) + tmtypes.UnittestOnlySetMilestoneEarthHeight(1) + tmtypes.UnittestOnlySetMilestoneVenus6Height(1) + + env := new(Env) + env.priv = make([]ethsecp256k1.PrivKey, 10) + env.addr = make([]sdk.AccAddress, 10) + for i := 0; i < 10; i++ { + priv, _ := ethsecp256k1.GenerateKey() + addr := sdk.AccAddress(priv.PubKey().Address()) + env.priv[i] = priv + env.addr[i] = addr + } + chainA, chainB := NewChain(env), NewChain(env) + + VMBPrecompileSetup(t, chainA) + VMBPrecompileSetup(t, chainB) + + DeployContractAndGetContractAddress(t, chainA) + DeployContractAndGetContractAddress(t, chainB) + + testCases := []struct { + title string + executeTxs func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) + expectedCodes []uint32 + }{ + // ##################### + // ### only evm txs #### + // ##################### + { + "5 evm txs, 1 group: a->b b->c c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + for i := 0; i < 5; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + { + "4 evm txs and 1 AnteErr evm tx, 1 group: a->b anteErr(a->b) b->c c->d d->e", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + for i := 2; i < 5; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 0, 0, 0}, + }, + { + "4 evm txs and 1 AnteErr evm tx, 2 group: a->b anteErr(a->b) / c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + for i := 3; i < 6; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 0, 0, 0}, + }, + { + "5 failed evm txs, 1 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + for i := 0; i < 5; i++ { + rawTxs = append(rawTxs, createFailedEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{11, 11, 11, 11, 11}, + }, + { + "5 evm txs, 2 group:a->b b->c / d->e e->f f->g", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + //one group 3txs + for i := 0; i < 3; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + //one group 2txs + for i := 8; i > 6; i-- { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + { + "5 failed evm txs, 2 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + //one group 3txs + for i := 0; i < 3; i++ { + rawTxs = append(rawTxs, createFailedEthTx(t, chain, i)) + } + //one group 2txs + for i := 8; i > 6; i-- { + rawTxs = append(rawTxs, createFailedEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{11, 11, 11, 11, 11}, + }, + { + "2 evm txs and 3 failed evm txs, 2 group:a->b b->c / failed(d->e e->f f->g)", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + rawTxs := [][]byte{} + //one group 3txs + for i := 0; i < 3; i++ { + rawTxs = append(rawTxs, createFailedEthTx(t, chain, i)) + } + //one group 2txs + for i := 8; i > 6; i-- { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{11, 11, 11, 0, 0}, + }, + { + "3 evm txs and 2 failed evm txs, 2 group:a->b failed(b->c) / d->e e->f failed(f->g)", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + rawTxs := [][]byte{} + //one group 3txs + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 2)) + //one group 2txs + for i := 8; i > 7; i-- { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 7)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 11, 0, 11}, + }, + { + "3 contract txs and 2 normal evm txs, 2 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + + for i := 0; i < 3; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + for i := 8; i > 6; i-- { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + // ##################### + // ## only cosmos txs ## + // ##################### + { + "5 cosmos txs, 0 group: a->b b->c c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + for i := 0; i < 5; i++ { + rawTxs = append(rawTxs, createTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + { + "4 cosmos txs, 1 Failed cosmos tx, 0 group: a->b failed(b->c) / d->e e->f f->g", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 1)) + for i := 3; i < 6; i++ { + rawTxs = append(rawTxs, createTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 61034, 0, 0, 0}, + }, + { + "4 cosmos txs, 1 AnteErr cosmos tx, 0 group: a->b AnteErr(b->c) c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 1)) + for i := 2; i < 5; i++ { + rawTxs = append(rawTxs, createTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 5, 0, 0, 0}, + }, + { + "4 failed cosmos txs, 1 AnteErr cosmos tx, 0 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 1)) + for i := 2; i < 5; i++ { + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{61034, 5, 61034, 61034, 61034}, + }, + { + "3 cosmos txs, 1 failed cosmos tx, 1 AnteErr cosmos tx, 0 group: a->b b->c c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, createTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 61034, 5, 0, 0}, + }, + { + "5 failed cosmos txs, 0 group: a->b b->c c->d d->e e->f", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + + var rawTxs [][]byte + for i := 0; i < 5; i++ { + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{61034, 61034, 61034, 61034, 61034}, + }, + // ##################### + // ###### mix txs ###### + // ##################### + { + "2 evm txs with 1 cosmos tx and 2 evm contract txs, 2 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + //one group 3txs + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + //cosmos tx + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 4; i < 6; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + { + "2 evm txs, 1 cosmos tx, and 2 evm contract txs, 2 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 3)) + //one group 2txs + for i := 5; i < 7; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 0, 0}, + }, + { + "1 evm tx, 1 AnteErr evm tx, 1 cosmos tx, and 2 evm contract txs, 2 group", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 3)) + //one group 2txs + for i := 5; i < 7; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 0, 0, 0}, + }, + { + "1 evm tx, 1 failed evm tx, 1 cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 0, 0, 0}, + }, + { + "1 evm tx, 1 failed evm tx, 1 cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 0, 0, 0}, + }, + { + "2 evm tx, 1 AnteErr cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 5, 0, 0}, + }, + { + "2 evm tx, 1 failed cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 61034, 0, 0}, + }, + { + "2 evm tx, 1 cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 3, 0}, + }, + { + "2 evm tx, 1 cosmos tx, 1 failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 0, 11, 0}, + }, + { + "2 evm tx, 1 AnteErr cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 5, 0, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 AnteErr cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 5, 0, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 failed cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 61034, 0, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 0, 3, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 cosmos tx, 1 failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 0, 11, 0}, + }, + { + "1 evm tx, 1 failed evm, 1 AnteErr cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 5, 0, 0}, + }, + { + "1 evm tx, 1 failed evm, 1 failed cosmos tx, and 2 evm contract txs", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + for i := 3; i < 5; i++ { + rawTxs = append(rawTxs, callContract(t, chain, i)) + } + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 61034, 0, 0}, + }, + { + "1 evm tx, 1 failed evm, 1 cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 0, 3, 0}, + }, + { + "1 evm tx, 1 failed evm, 1 cosmos tx, 1 failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 0, 11, 0}, + }, + { + "2 evm tx, 1 AnteErr cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 5, 3, 0}, + }, + { + "2 evm tx, 1 AnteErr cosmos tx, 1 failed evm contract tx, and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 5, 11, 0}, + }, + { + "2 evm tx, 1 failed cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 61034, 3, 0}, + }, + { + "2 evm tx, 1 failed cosmos tx, 1 failed evm contract tx, and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + for i := 0; i < 2; i++ { + rawTxs = append(rawTxs, createEthTx(t, chain, i)) + } + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 61034, 11, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 AnteErr cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 5, 3, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 AnteErr cosmos tx, 1 Failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 5, 11, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 Failed cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 61034, 3, 0}, + }, + { + "1 evm tx, 1 Failed evm, 1 AnteErr cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 5, 3, 0}, + }, + { + "1 evm tx, 1 Failed evm, 1 Failed cosmos tx, 1 Failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 61034, 11, 0}, + }, + { + "1 evm tx, 1 Failed evm, 1 Failed cosmos tx, 1 AnteErr evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractAnteErr(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 61034, 3, 0}, + }, + { + "1 evm tx, 1 Failed evm, 1 AnteErr cosmos tx, 1 Failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createFailedEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createAnteErrTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 11, 5, 11, 0}, + }, + { + "1 evm tx, 1 AnteErr evm, 1 Failed cosmos tx, 1 Failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, createAnteErrEthTx(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 3, 61034, 11, 0}, + }, + { + "1 evm tx, 1 callWasm vmb tx, 1 Failed cosmos tx, 1 Failed evm contract txs,and 1 evm contract tx", + func(t *testing.T, chain *Chain, isParallel bool) ([]*abci.ResponseDeliverTx, []byte, []byte) { + var rawTxs [][]byte + rawTxs = append(rawTxs, createEthTx(t, chain, 0)) + rawTxs = append(rawTxs, callWasmAtContractA(t, chain, 1)) + rawTxs = append(rawTxs, createFailedTokenSendTx(t, chain, 2)) + //one group 2txs + rawTxs = append(rawTxs, callContractFailed(t, chain, 3)) + rawTxs = append(rawTxs, callContract(t, chain, 4)) + ret := runTxs(chain, rawTxs, isParallel) + + return ret, resultHash(ret), chain.app.BaseApp.LastCommitID().Hash + }, + []uint32{0, 0, 61034, 11, 0}, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + retA, resultHashA, appHashA := tc.executeTxs(t, chainA, true) + retB, resultHashB, appHashB := tc.executeTxs(t, chainB, false) + checkCodes(t, tc.title, retA, tc.expectedCodes) + checkCodes(t, tc.title, retB, tc.expectedCodes) + require.True(t, reflect.DeepEqual(resultHashA, resultHashB)) + require.True(t, reflect.DeepEqual(appHashA, appHashB)) + }) + } +} + +func resultHash(txs []*abci.ResponseDeliverTx) []byte { + results := tmtypes.NewResults(txs) + return results.Hash() +} + +// contract Storage { +// uint256 number; +// /** +// * @dev Store value in variable +// * @param num value to store +// */ +// function store(uint256 num) public { +// number = num; +// } +// function add() public { +// number += 1; +// } +// /** +// * @dev Return value +// * @return value of 'number' +// */ +// function retrieve() public view returns (uint256){ +// return number; +// } +// } +var abiStr = `[{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}]` + +func deployContract(t *testing.T, chain *Chain, i int) []byte { + // Deploy contract - Owner.sol + gasLimit := uint64(30000000) + gasPrice := big.NewInt(100000000) + + //sender := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) + + bytecode := ethcmn.FromHex("608060405234801561001057600080fd5b50610217806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631003e2d2146100465780632e64cec1146100625780636057361d14610080575b600080fd5b610060600480360381019061005b9190610105565b61009c565b005b61006a6100b7565b6040516100779190610141565b60405180910390f35b61009a60048036038101906100959190610105565b6100c0565b005b806000808282546100ad919061018b565b9250508190555050565b60008054905090565b8060008190555050565b600080fd5b6000819050919050565b6100e2816100cf565b81146100ed57600080fd5b50565b6000813590506100ff816100d9565b92915050565b60006020828403121561011b5761011a6100ca565b5b6000610129848285016100f0565b91505092915050565b61013b816100cf565b82525050565b60006020820190506101566000830184610132565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610196826100cf565b91506101a1836100cf565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101d6576101d561015c565b5b82820190509291505056fea2646970667358221220318e29d6b4806f219eedd0cc861e82c13e28eb7f42161f2c780dc539b0e32b4e64736f6c634300080a0033") + msg := evmtypes.NewMsgEthereumTx(chain.seq[i], nil, big.NewInt(0), gasLimit, gasPrice, bytecode) + err := msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) + require.NoError(t, err) + chain.seq[i]++ + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + return rawTx +} + +type CompiledContract struct { + ABI abi.ABI + Bin string +} + +func UnmarshalContract(t *testing.T, cJson string) *CompiledContract { + cc := new(CompiledContract) + err := json.Unmarshal([]byte(cJson), cc) + require.NoError(t, err) + return cc +} + +func callContract(t *testing.T, chain *Chain, i int) []byte { + gasLimit := uint64(30000000) + gasPrice := big.NewInt(100000000) + //to := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) + to := ethcmn.BytesToAddress(chain.ContractAddr) + cc := UnmarshalContract(t, contractJson) + data, err := cc.ABI.Pack("add") + require.NoError(t, err) + msg := evmtypes.NewMsgEthereumTx(chain.seq[i], &to, big.NewInt(0), gasLimit, gasPrice, data) + err = msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) + require.NoError(t, err) + chain.seq[i]++ + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + return rawTx +} + +func callWasmAtContractA(t *testing.T, chain *Chain, i int) []byte { + gasLimit := uint64(30000000) + gasPrice := big.NewInt(100000000) + + to := ethcmn.BytesToAddress(chain.VMBWasmContract.Bytes()) + cc := UnmarshalContract(t, testPrecompileABIAJson) + wasmCallData := fmt.Sprintf(callWasmMsgFormat, 10, chain.addr[i].String()) + data, err := cc.ABI.Pack("callWasm", chain.VMBWasmContract.String(), hex.EncodeToString([]byte(wasmCallData)), true) + require.NoError(t, err) + msg := evmtypes.NewMsgEthereumTx(chain.seq[i], &to, big.NewInt(0), gasLimit, gasPrice, data) + err = msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) + require.NoError(t, err) + chain.seq[i]++ + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + return rawTx +} + +func callContractFailed(t *testing.T, chain *Chain, i int) []byte { + gasLimit := uint64(1) + gasPrice := big.NewInt(100000000) + //to := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) + to := ethcmn.BytesToAddress(chain.ContractAddr) + cc := UnmarshalContract(t, contractJson) + data, err := cc.ABI.Pack("add") + require.NoError(t, err) + msg := evmtypes.NewMsgEthereumTx(chain.seq[i], &to, big.NewInt(0), gasLimit, gasPrice, data) + err = msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) + require.NoError(t, err) + chain.seq[i]++ + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + return rawTx +} + +func callContractAnteErr(t *testing.T, chain *Chain, i int) []byte { + gasLimit := uint64(30000000) + gasPrice := big.NewInt(100000000) + //to := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) + to := ethcmn.BytesToAddress(chain.ContractAddr) + cc := UnmarshalContract(t, contractJson) + data, err := cc.ABI.Pack("add") + require.NoError(t, err) + msg := evmtypes.NewMsgEthereumTx(chain.seq[i]+1, &to, big.NewInt(0), gasLimit, gasPrice, data) + err = msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) + require.NoError(t, err) + rawTx, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err) + return rawTx +} + +func checkCodes(t *testing.T, title string, resp []*abci.ResponseDeliverTx, codes []uint32) { + for i, code := range codes { + require.True(t, resp[i].Code == code, "title: %s, expect code: %d, but %d! tx index: %d", title, code, resp[i].Code, i) + } +} + +func VMBPrecompileSetup(t *testing.T, chain *Chain) { + timeValue := fmt.Sprintf("%d-04-11 13:33:37", chain.timeYear+1) + testTime, _ := time.Parse("2006-01-02 15:04:05", timeValue) + header := abci.Header{Height: chain.app.LastBlockHeight() + 1, ChainID: chain.chainIdStr, Time: testTime} + chain.app.BaseApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + chain.VMBContractA = vmbDeployEvmContract(t, chain, testPrecompileCodeA) + initMsg := []byte(fmt.Sprintf("{\"decimals\":10,\"initial_balances\":[{\"address\":\"%s\",\"amount\":\"100000000\"}],\"name\":\"my test token\", \"symbol\":\"MTT\"}", chain.addr[0].String())) + chain.VMBWasmContract = vmbDeployWasmContract(t, chain, "precompile.wasm", initMsg) + + chain.app.BaseApp.EndBlock(abci.RequestEndBlock{}) + chain.app.BaseApp.Commit(abci.RequestCommit{}) +} + +func vmbDeployEvmContract(t *testing.T, chain *Chain, code string) ethcmn.Address { + freeCallBytecode := ethcmn.Hex2Bytes(code) + _, contract, err := chain.app.VMBridgeKeeper.CallEvm(chain.Ctx(), ethcmn.BytesToAddress(chain.addr[0]), nil, big.NewInt(0), freeCallBytecode) + require.NoError(t, err) + chain.seq[0]++ + return contract.ContractAddress +} + +func vmbDeployWasmContract(t *testing.T, chain *Chain, filename string, initMsg []byte) sdk.WasmAddress { + wasmcode, err := ioutil.ReadFile(fmt.Sprintf("./testdata/%s", filename)) + require.NoError(t, err) + codeid, err := chain.app.WasmPermissionKeeper.Create(chain.Ctx(), sdk.AccToAWasmddress(chain.addr[0]), wasmcode, nil) + require.NoError(t, err) + //initMsg := []byte(fmt.Sprintf("{\"decimals\":10,\"initial_balances\":[{\"address\":\"%s\",\"amount\":\"100000000\"}],\"name\":\"my test token\", \"symbol\":\"MTT\"}", suite.addr.String())) + contract, _, err := chain.app.WasmPermissionKeeper.Instantiate(chain.Ctx(), codeid, sdk.AccToAWasmddress(chain.addr[0]), sdk.AccToAWasmddress(chain.addr[0]), initMsg, "label", sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}) + require.NoError(t, err) + return contract +} diff --git a/app/app_test.go b/app/app_test.go index cdbc799926..898301b443 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -7,35 +7,30 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/okex/exchain/app/crypto/ethsecp256k1" + "github.com/okex/exchain/libs/cosmos-sdk/codec" cosmossdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/cosmos-sdk/x/auth" authclient "github.com/okex/exchain/libs/cosmos-sdk/x/auth/client/utils" + authtypes "github.com/okex/exchain/libs/cosmos-sdk/x/auth/types" + "github.com/okex/exchain/libs/cosmos-sdk/x/upgrade" + abci "github.com/okex/exchain/libs/tendermint/abci/types" + abcitypes "github.com/okex/exchain/libs/tendermint/abci/types" + "github.com/okex/exchain/libs/tendermint/crypto" "github.com/okex/exchain/libs/tendermint/global" + "github.com/okex/exchain/libs/tendermint/libs/log" tendertypes "github.com/okex/exchain/libs/tendermint/types" - "github.com/okex/exchain/x/distribution/keeper" - evmtypes "github.com/okex/exchain/x/evm/types" - - "github.com/okex/exchain/libs/cosmos-sdk/x/upgrade" + dbm "github.com/okex/exchain/libs/tm-db" "github.com/okex/exchain/x/dex" distr "github.com/okex/exchain/x/distribution" + "github.com/okex/exchain/x/distribution/keeper" + evmtypes "github.com/okex/exchain/x/evm/types" "github.com/okex/exchain/x/farm" - "github.com/okex/exchain/x/params" - - "github.com/stretchr/testify/require" - - abci "github.com/okex/exchain/libs/tendermint/abci/types" - "github.com/okex/exchain/libs/tendermint/libs/log" - dbm "github.com/okex/exchain/libs/tm-db" - - "github.com/okex/exchain/libs/cosmos-sdk/codec" - - "github.com/okex/exchain/libs/cosmos-sdk/x/auth" - authtypes "github.com/okex/exchain/libs/cosmos-sdk/x/auth/types" - abcitypes "github.com/okex/exchain/libs/tendermint/abci/types" - "github.com/okex/exchain/libs/tendermint/crypto" "github.com/okex/exchain/x/gov" + "github.com/okex/exchain/x/params" ) var ( diff --git a/app/test_helpers.go b/app/test_helpers.go index 989c903ea7..1603e44f7d 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -1,11 +1,16 @@ package app import ( + "time" + "github.com/spf13/viper" "github.com/okex/exchain/libs/cosmos-sdk/codec" sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + authtypes "github.com/okex/exchain/libs/cosmos-sdk/x/auth" + authexported "github.com/okex/exchain/libs/cosmos-sdk/x/auth/exported" abci "github.com/okex/exchain/libs/tendermint/abci/types" + abcitypes "github.com/okex/exchain/libs/tendermint/abci/types" "github.com/okex/exchain/libs/tendermint/libs/log" "github.com/okex/exchain/libs/tendermint/types" dbm "github.com/okex/exchain/libs/tm-db" @@ -54,3 +59,41 @@ func Setup(isCheckTx bool, options ...Option) *OKExChainApp { return app } + +func SetupWithGenesisAccounts(isCheckTx bool, genAccs []authexported.GenesisAccount, options ...Option) *OKExChainApp { + viper.Set(sdk.FlagDBBackend, string(dbm.MemDBBackend)) + types.DBBackend = string(dbm.MemDBBackend) + db := dbm.NewMemDB() + app := NewOKExChainApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, 0) + + if !isCheckTx { + setupOption := &SetupOption{chainId: ""} + for _, opt := range options { + opt(setupOption) + } + // init chain must be called to stop deliverState from being nil + genesisState := NewDefaultGenesisState() + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = app.Codec().MustMarshalJSON(authGenesis) + stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState) + if err != nil { + panic(err) + } + + // Initialize the chain + testTime, _ := time.Parse("2006-01-02 15:04:05", "2017-04-11 13:33:37") + app.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + ChainId: setupOption.chainId, + Time: testTime, + }, + ) + + app.Commit(abcitypes.RequestCommit{}) + app.BeginBlock(abci.RequestBeginBlock{Header: abcitypes.Header{Height: app.LastBlockHeight() + 1}}) + } + + return app +} diff --git a/app/testdata/cw20.wasm b/app/testdata/cw20.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9ad8edb81314d9dc618f37d9e356a5918694d658 GIT binary patch literal 171387 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.7.0 <0.9.0; + + +contract FreeCall { + address public constant moduleAddress = + address(0x1033796B018B2bf0Fc9CB88c0793b2F275eDB624); + + event __OKCCallToWasm(string wasmAddr, uint256 value, string data); + + function callByWasm(string memory callerWasmAddr,string memory data) public payable returns (string memory response) { + string memory temp1 = strConcat("callByWasm return: ",callerWasmAddr); + string memory temp2 = strConcat(temp1," ---data: "); + string memory temp3 = strConcat(temp2,data); + return temp3; + } + + + function callToWasm(string memory wasmAddr, uint256 value, string memory data) public returns (bool success){ + emit __OKCCallToWasm(wasmAddr,value,data); + return true; + } + + + function strConcat(string memory _a, string memory _b) internal returns (string memory){ + bytes memory _ba = bytes(_a); + bytes memory _bb = bytes(_b); + string memory ret = new string(_ba.length + _bb.length); + bytes memory bret = bytes(ret); + uint k = 0; + for (uint i = 0; i < _ba.length; i++) { + bret[k++] = _ba[i]; + } + for (uint i = 0; i < _bb.length; i++) { + bret[k++] = _bb[i]; + } + return string(ret); + } +} diff --git a/app/testdata/freecall.wasm b/app/testdata/freecall.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8cf65f6b8782d17bb052ba974978a383cfb391f8 GIT binary patch literal 188436 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 literal 0 HcmV?d00001 diff --git a/app/testdata/precompile.sol b/app/testdata/precompile.sol new file mode 100644 index 0000000000..9d8aa653ab --- /dev/null +++ b/app/testdata/precompile.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.0 <0.9.0; + +contract ContractA { + + address precomplieContarct = 0x0000000000000000000000000000000000000100; + uint256 public number; + event pushLog(string data); + + function callWasm(string memory wasmAddr, string memory msgData,bool requireASuccess) public payable returns (bytes memory response){ + number = number + 1; + (bool success, bytes memory data) = precomplieContarct.call{value: msg.value} ( + abi.encodeWithSignature("callToWasm(string,string)", wasmAddr,msgData) + ); + if (requireASuccess) { + require(success); + string memory res = abi.decode(data,(string)); + emit pushLog(res); + } + number = number + 1; + return data; + } + + function queryWasm(string memory msgData,bool requireASuccess) public payable returns (bytes memory response){ + number = number + 1; + (bool success, bytes memory data) = precomplieContarct.call{value: msg.value} ( + abi.encodeWithSignature("queryToWasm(string)",msgData) + ); + if (requireASuccess) { + require(success); + string memory res = abi.decode(data,(string)); + emit pushLog(res); + } + number = number + 1; + return data; + } + + function callToWasm(string memory wasmAddr, string memory data) public payable returns (string memory response) { + return ""; + } + + function queryToWasm(string memory data) public view returns (string memory response) { + return ""; + } +} + +contract ContractB { + uint256 public number; + + function callWasm(address contractA ,string memory wasmAddr, string memory msgData, bool requireASuccess,bool requireBSuccess) public payable returns (bytes memory response){ + number = number + 1; + (bool success, bytes memory data) = contractA.call{value: msg.value} ( + abi.encodeWithSignature("callWasm(string,string,bool)", wasmAddr,msgData,requireASuccess) + ); + number = number + 1; + if (requireBSuccess) { + require(success); + } + return data; + } + + function queryWasm(address contractA , string memory msgData, bool requireASuccess,bool requireBSuccess) public payable returns (bytes memory response){ + number = number + 1; + (bool success, bytes memory data) = contractA.call{value: msg.value} ( + abi.encodeWithSignature("queryWasm(string,bool)",msgData,requireASuccess) + ); + number = number + 1; + if (requireBSuccess) { + require(success); + } + return data; + } +} diff --git a/app/testdata/precompile.wasm b/app/testdata/precompile.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c79e6e9f42b1e11c60ebf2366f2b0a96513340b8 GIT binary patch literal 188738 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 VZkPOSV~x3k-#UH^__3b+e*qb!F0KFo literal 0 HcmV?d00001 diff --git a/app/testdata/testerc20.sol b/app/testdata/testerc20.sol new file mode 100644 index 0000000000..04117671b6 --- /dev/null +++ b/app/testdata/testerc20.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.7; + +contract Exchange is ERC20 { + address public constant moduleAddress = + address(0xc63cf6c8E1f3DF41085E9d8Af49584dae1432b4f); + + string public wasmContractAddress = "ex14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s6fqu27"; + + event __OKCSendToWasm(string wasmAddr, string recipient, uint256 amount); + + function initialize(string memory denom_, uint8 decimals_) public { + __ERC20_init(denom_, denom_, decimals_); + } + + function native_denom() public view returns (string memory) { + return symbol(); + } + + function updatewasmContractAddress(string memory addr) public { + wasmContractAddress = addr; + } + + function mint(address recipient,uint256 amount) public { + _mint(recipient, amount); + } + + + function mintERC20(string calldata caller, address recipient,uint256 amount) public returns (bool) { + require(msg.sender == moduleAddress); + require(keccak256(abi.encodePacked(caller)) == keccak256(abi.encodePacked(wasmContractAddress))); + _mint(recipient, amount); + return true; + } + + + // send an "amount" of the contract token to recipient on wasm + function send_to_wasm(string memory recipient,string memory wasmContract , uint256 amount) public { + _burn(msg.sender, amount); + emit __OKCSendToWasm(wasmContract,recipient, amount); + } +} + +contract ERC20 { + bool private initialized; + + string private _name; + string private _symbol; + uint8 private _decimals; + uint256 private _totalSupply; + + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + function __ERC20_init( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) internal { + require(!initialized, "ERC20: already initialized;"); + initialized = true; + + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + } + + function name() public view virtual returns (string memory) { + return _name; + } + + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + function decimals() public view virtual returns (uint8) { + return _decimals; + } + + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) + public + virtual + returns (bool) + { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) + public + view + virtual + returns (uint256) + { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) + public + virtual + returns (bool) + { + address owner = msg.sender; + _approve(owner, spender, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) + public + virtual + returns (bool) + { + address owner = msg.sender; + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + address owner = msg.sender; + uint256 currentAllowance = _allowances[owner][spender]; + require( + currentAllowance >= subtractedValue, + "ERC20: decreased allowance below zero" + ); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + uint256 fromBalance = _balances[from]; + require( + fromBalance >= amount, + "ERC20: transfer amount exceeds balance" + ); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + } + + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + } + + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + } + + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require( + currentAllowance >= amount, + "ERC20: insufficient allowance" + ); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } +} diff --git a/libs/cosmos-sdk/baseapp/baseapp_paralled_test.go b/libs/cosmos-sdk/baseapp/baseapp_paralled_test.go deleted file mode 100644 index d2ae3e815b..0000000000 --- a/libs/cosmos-sdk/baseapp/baseapp_paralled_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package baseapp_test - -import ( - "encoding/json" - "math/big" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/require" - - "github.com/okex/exchain/app/crypto/ethsecp256k1" - types3 "github.com/okex/exchain/app/types" - "github.com/okex/exchain/libs/cosmos-sdk/simapp/helpers" - sdk "github.com/okex/exchain/libs/cosmos-sdk/types" - "github.com/okex/exchain/libs/cosmos-sdk/x/auth" - authexported "github.com/okex/exchain/libs/cosmos-sdk/x/auth/exported" - simapp2 "github.com/okex/exchain/libs/ibc-go/testing/simapp" - abci "github.com/okex/exchain/libs/tendermint/abci/types" - types2 "github.com/okex/exchain/libs/tendermint/types" - "github.com/okex/exchain/x/evm/types" - types4 "github.com/okex/exchain/x/token/types" -) - -type Env struct { - priv []ethsecp256k1.PrivKey - addr []sdk.AccAddress -} -type Chain struct { - app *simapp2.SimApp - priv []ethsecp256k1.PrivKey - addr []sdk.AccAddress - acc []*types3.EthAccount - seq []uint64 - num []uint64 - chainIdStr string - chainIdInt *big.Int - ContractAddr []byte -} - -func NewChain(env *Env) *Chain { - types2.UnittestOnlySetMilestoneVenusHeight(-1) - types2.UnittestOnlySetMilestoneVenus1Height(-1) - chain := new(Chain) - chain.acc = make([]*types3.EthAccount, 10) - chain.priv = make([]ethsecp256k1.PrivKey, 10) - chain.addr = make([]sdk.AccAddress, 10) - chain.seq = make([]uint64, 10) - chain.num = make([]uint64, 10) - genAccs := []authexported.GenesisAccount{} - for i := 0; i < 10; i++ { - chain.acc[i] = &types3.EthAccount{ - BaseAccount: &auth.BaseAccount{ - Address: env.addr[i], - Coins: sdk.Coins{sdk.NewInt64Coin("okt", 1000000)}, - }, - //CodeHash: []byte{1, 2}, - } - genAccs = append(genAccs, chain.acc[i]) - chain.priv[i] = env.priv[i] - chain.addr[i] = env.addr[i] - chain.seq[i] = 0 - chain.num[i] = uint64(i) - } - chain.chainIdStr = "ethermint-3" - chain.chainIdInt = big.NewInt(3) - - chain.app = simapp2.SetupWithGenesisAccounts(genAccs, sdk.NewDecCoins(sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(1000000, 0)))) - //header := abci.Header{Height: app.LastBlockHeight() + 1, ChainID: chainIdStr} - - chain.app.BaseApp.Commit(abci.RequestCommit{}) - return chain -} - -func createEthTx(t *testing.T, chain *Chain, i int) []byte { - amount, gasPrice, gasLimit := int64(1024), int64(2048), uint64(100000) - addrTo := ethcmn.BytesToAddress(chain.priv[i+1].PubKey().Address().Bytes()) - msg := types.NewMsgEthereumTx(chain.seq[i], &addrTo, big.NewInt(amount), gasLimit, big.NewInt(gasPrice), []byte("test")) - chain.seq[i]++ - err := msg.Sign(chain.chainIdInt, chain.priv[i].ToECDSA()) - require.NoError(t, err) - rawtx, err := rlp.EncodeToBytes(&msg) - require.NoError(t, err) - - return rawtx -} - -//contract Storage { -//uint256 number; -///** -// * @dev Store value in variable -// * @param num value to store -// */ -//function store(uint256 num) public { -//number = num; -//} -//function add() public { -//number += 1; -//} -///** -// * @dev Return value -// * @return value of 'number' -// */ -//function retrieve() public view returns (uint256){ -//return number; -//} -//} -var abiStr = `[{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}]` - -func deployContract(t *testing.T, chain *Chain, i int) []byte { - // Deploy contract - Owner.sol - gasLimit := uint64(10000000000000) - gasPrice := big.NewInt(10000) - - sender := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) - - bytecode := ethcmn.FromHex("608060405234801561001057600080fd5b50610217806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631003e2d2146100465780632e64cec1146100625780636057361d14610080575b600080fd5b610060600480360381019061005b9190610105565b61009c565b005b61006a6100b7565b6040516100779190610141565b60405180910390f35b61009a60048036038101906100959190610105565b6100c0565b005b806000808282546100ad919061018b565b9250508190555050565b60008054905090565b8060008190555050565b600080fd5b6000819050919050565b6100e2816100cf565b81146100ed57600080fd5b50565b6000813590506100ff816100d9565b92915050565b60006020828403121561011b5761011a6100ca565b5b6000610129848285016100f0565b91505092915050565b61013b816100cf565b82525050565b60006020820190506101566000830184610132565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610196826100cf565b91506101a1836100cf565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101d6576101d561015c565b5b82820190509291505056fea2646970667358221220318e29d6b4806f219eedd0cc861e82c13e28eb7f42161f2c780dc539b0e32b4e64736f6c634300080a0033") - msg := types.NewMsgEthereumTx(chain.seq[i], &sender, big.NewInt(0), gasLimit, gasPrice, bytecode) - err := msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) - require.NoError(t, err) - chain.seq[i]++ - rawTx, err := rlp.EncodeToBytes(&msg) - require.NoError(t, err) - return rawTx -} - -var contractJson = `{"abi":[{"inputs":[],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"608060405234801561001057600080fd5b50610205806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632e64cec1146100465780634f2be91f146100645780636057361d1461006e575b600080fd5b61004e61008a565b60405161005b91906100d1565b60405180910390f35b61006c610093565b005b6100886004803603810190610083919061011d565b6100ae565b005b60008054905090565b60016000808282546100a59190610179565b92505081905550565b8060008190555050565b6000819050919050565b6100cb816100b8565b82525050565b60006020820190506100e660008301846100c2565b92915050565b600080fd5b6100fa816100b8565b811461010557600080fd5b50565b600081359050610117816100f1565b92915050565b600060208284031215610133576101326100ec565b5b600061014184828501610108565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610184826100b8565b915061018f836100b8565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101c4576101c361014a565b5b82820190509291505056fea2646970667358221220742b7232e733bee3592cb9e558bdae3fbd0006bcbdba76abc47b6020744037b364736f6c634300080a0033"}` - -type CompiledContract struct { - ABI abi.ABI - Bin string -} - -func UnmarshalContract(t *testing.T) *CompiledContract { - cc := new(CompiledContract) - err := json.Unmarshal([]byte(contractJson), cc) - require.NoError(t, err) - return cc -} - -func callContract(t *testing.T, chain *Chain, i int) []byte { - gasLimit := uint64(10000000000000) - gasPrice := big.NewInt(10000) - //to := ethcmn.HexToAddress(chain.priv[i].PubKey().Address().String()) - to := ethcmn.BytesToAddress(chain.ContractAddr) - cc := UnmarshalContract(t) - data, err := cc.ABI.Pack("add") - require.NoError(t, err) - msg := types.NewMsgEthereumTx(chain.seq[i], &to, big.NewInt(0), gasLimit, gasPrice, data) - err = msg.Sign(big.NewInt(3), chain.priv[i].ToECDSA()) - require.NoError(t, err) - chain.seq[i]++ - rawTx, err := rlp.EncodeToBytes(&msg) - require.NoError(t, err) - return rawTx -} - -func createCosmosTx(t *testing.T, chain *Chain, i int) []byte { - msg := types4.NewMsgTokenSend(chain.addr[i], chain.addr[i+1], sdk.Coins{sdk.NewInt64Coin("okt", 10)}) - - tx := helpers.GenTx( - []sdk.Msg{msg}, - sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, - helpers.DefaultGenTxGas, - chain.chainIdStr, - []uint64{chain.num[i]}, - []uint64{chain.seq[i]}, - chain.priv[i], - ) - chain.seq[i]++ - - txBytes, err := chain.app.Codec().MarshalBinaryLengthPrefixed(tx) - require.Nil(t, err) - return txBytes -} - -func runtxs(chain *Chain, rawTxs [][]byte, isParalle bool) []*abci.ResponseDeliverTx { - header := abci.Header{Height: chain.app.LastBlockHeight() + 1, ChainID: chain.chainIdStr} - chain.app.BaseApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ret := []*abci.ResponseDeliverTx{} - if isParalle { - ret = chain.app.BaseApp.ParallelTxs(rawTxs, false) - } else { - for _, tx := range rawTxs { - r := chain.app.BaseApp.DeliverTx(abci.RequestDeliverTx{Tx: tx}) - ret = append(ret, &r) - } - } - chain.app.BaseApp.EndBlock(abci.RequestEndBlock{}) - chain.app.BaseApp.Commit(abci.RequestCommit{}) - - return ret -} - -func DeployContractAndGetContractAddress(t *testing.T, chain *Chain) { - rawTxs := [][]byte{} - rawTxs = append(rawTxs, deployContract(t, chain, 0)) - r := runtxs(chain, rawTxs, false) - - for _, e := range r[0].Events { - for _, v := range e.Attributes { - if string(v.Key) == "recipient" { - chain.ContractAddr = v.Value - } - } - } -} - -func TestParalledTxs(t *testing.T) { - env := new(Env) - accountNum := 10 - env.priv = make([]ethsecp256k1.PrivKey, 10) - env.addr = make([]sdk.AccAddress, 10) - for i := 0; i < accountNum; i++ { - priv, _ := ethsecp256k1.GenerateKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - env.priv[i] = priv - env.addr[i] = addr - } - - chainA, chainB := NewChain(env), NewChain(env) - //deploy contract on chainA and chainB - DeployContractAndGetContractAddress(t, chainA) - DeployContractAndGetContractAddress(t, chainB) - - testCases := []struct { - name string - malleate func(t *testing.T, chain *Chain, isParallel bool) ([]byte, []byte) - }{ - { - "five txs one group:a->b b->c c->d d->e e->f", - func(t *testing.T, chain *Chain, isParalled bool) ([]byte, []byte) { - - rawTxs := [][]byte{} - for i := 0; i < 5; i++ { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - - header := abci.Header{Height: chain.app.LastBlockHeight() + 1, ChainID: chain.chainIdStr} - chain.app.BaseApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ret := runtxs(chain, rawTxs, isParalled) - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - { - "five txs two group, no conflict:a->b b->c / d->e e->f f->g", - func(t *testing.T, chain *Chain, isParalled bool) ([]byte, []byte) { - rawTxs := [][]byte{} - //one group 3txs - for i := 0; i < 3; i++ { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - //one group 2txs - for i := 8; i > 6; i-- { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - ret := runtxs(chain, rawTxs, isParalled) - - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - { - "five txs two group, has conflict", - func(t *testing.T, chain *Chain, isParalled bool) ([]byte, []byte) { - rawTxs := [][]byte{} - - //one group 3txs - for i := 0; i < 3; i++ { - rawTxs = append(rawTxs, callContract(t, chain, i)) - } - ////one group 2txs - for i := 8; i > 6; i-- { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - ret := runtxs(chain, rawTxs, isParalled) - - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - { - "five txs one group with cosmos tx", - func(t *testing.T, chain *Chain, isParalled bool) ([]byte, []byte) { - rawTxs := [][]byte{} - //one group 3txs - for i := 0; i < 2; i++ { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - //cosmostx - rawTxs = append(rawTxs, createCosmosTx(t, chain, 2)) - //one group 2txs - for i := 3; i < 5; i++ { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - ret := runtxs(chain, rawTxs, isParalled) - - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - { - "five txs two group, no conflict with cosmos tx", - func(t *testing.T, chain *Chain, isParalle bool) ([]byte, []byte) { - rawTxs := [][]byte{} - //one group 3txs(2eth and 1 cosmos) - for i := 0; i < 2; i++ { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - //cosmos tx - rawTxs = append(rawTxs, createCosmosTx(t, chain, 2)) - //one group 2txs - for i := 8; i > 6; i-- { - rawTxs = append(rawTxs, createEthTx(t, chain, i)) - } - ret := runtxs(chain, rawTxs, isParalle) - - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - { - "five txs two group, has conflict with cosmos tx", - func(t *testing.T, chain *Chain, isParalled bool) ([]byte, []byte) { - rawTxs := [][]byte{} - - //one group 3txs:2 evm tx with conflict, one cosmos tx - for i := 0; i < 2; i++ { - rawTxs = append(rawTxs, callContract(t, chain, i)) - } - rawTxs = append(rawTxs, createCosmosTx(t, chain, 2)) - ////one group 2txs - for i := 8; i > 6; i-- { - rawTxs = append(rawTxs, createCosmosTx(t, chain, i)) - } - ret := runtxs(chain, rawTxs, isParalled) - - return resultHash(ret), chain.app.BaseApp.LastCommitID().Hash - }, - }, - } - for _, tc := range testCases { - resultHashA, appHashA := tc.malleate(t, chainA, true) - resultHashB, appHashB := tc.malleate(t, chainB, false) - require.True(t, reflect.DeepEqual(resultHashA, resultHashB)) - require.True(t, reflect.DeepEqual(appHashA, appHashB)) - } - -} - -func resultHash(txs []*abci.ResponseDeliverTx) []byte { - results := types2.NewResults(txs) - return results.Hash() -} diff --git a/libs/tendermint/types/milestone.go b/libs/tendermint/types/milestone.go index 0b6762761c..85e7f78195 100644 --- a/libs/tendermint/types/milestone.go +++ b/libs/tendermint/types/milestone.go @@ -201,6 +201,10 @@ func UnittestOnlySetMilestoneVenus1Height(h int64) { milestoneVenus1Height = h } +func UnittestOnlySetMilestoneVenus6Height(h int64) { + milestoneVenus6Height = h +} + func GetVenus1Height() int64 { return milestoneVenus1Height }