Skip to content

Commit

Permalink
Emit EVM contract events for EVM calls made from Go contracts (#1401)
Browse files Browse the repository at this point in the history
Co-authored-by: Vadim Macagon <[email protected]>
  • Loading branch information
Sriep and enlight committed Sep 1, 2019
1 parent 3a8ccbf commit 58da756
Show file tree
Hide file tree
Showing 29 changed files with 1,027 additions and 67 deletions.
38 changes: 25 additions & 13 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

"github.com/go-kit/kit/metrics"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
loom "github.com/loomnetwork/go-loom"
"github.com/loomnetwork/go-loom"
cctypes "github.com/loomnetwork/go-loom/builtin/types/chainconfig"
"github.com/loomnetwork/go-loom/plugin"
"github.com/loomnetwork/go-loom/types"
Expand All @@ -24,6 +24,7 @@ import (
stdprometheus "github.com/prometheus/client_golang/prometheus"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/common"
ttypes "github.com/tendermint/tendermint/types"
)

type ReadOnlyState interface {
Expand Down Expand Up @@ -338,6 +339,7 @@ type Application struct {
GetValidatorSet GetValidatorSet
EventStore store.EventStore
config *cctypes.Config
childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs
}

var _ abci.Application = &Application{}
Expand Down Expand Up @@ -656,7 +658,6 @@ func (a *Application) DeliverTx(txBytes []byte) abci.ResponseDeliverTx {
}

func (a *Application) processTx(txBytes []byte, isCheckTx bool) (TxHandlerResult, error) {
var err error
//TODO we should be keeping this across multiple checktx, and only rolling back after they all complete
// for now the nonce will have a special cache that it rolls back each block
storeTx := store.WrapAtomic(a.Store).BeginTx()
Expand All @@ -670,25 +671,34 @@ func (a *Application) processTx(txBytes []byte, isCheckTx bool) (TxHandlerResult
).WithOnChainConfig(a.config)

receiptHandler := a.ReceiptHandlerProvider.Store()
defer receiptHandler.DiscardCurrentReceipt()

r, err := a.TxHandler.ProcessTx(state, txBytes, isCheckTx)
if err != nil {
storeTx.Rollback()
// TODO: save receipt & hash of failed EVM tx to node-local persistent cache (not app state)
receiptHandler.DiscardCurrentReceipt()
return r, err
}

if !isCheckTx {
if r.Info == utils.CallEVM || r.Info == utils.DeployEvm {
err := a.EventHandler.LegacyEthSubscriptionSet().EmitTxEvent(r.Data, r.Info)
if err != nil {
log.Error("Emit Tx Event error", "err", err)
if err := a.EventHandler.LegacyEthSubscriptionSet().EmitTxEvent(r.Data, r.Info); err != nil {
log.Error("Emit Tx Event error", "err", err)
}

reader := a.ReceiptHandlerProvider.Reader()
if reader.GetCurrentReceipt() != nil {
receiptTxHash := reader.GetCurrentReceipt().TxHash
if err := a.EventHandler.EthSubscriptionSet().EmitTxEvent(receiptTxHash); err != nil {
log.Error("failed to emit tx event to subscribers", "err", err)
}
reader := a.ReceiptHandlerProvider.Reader()
if reader.GetCurrentReceipt() != nil {
if err = a.EventHandler.EthSubscriptionSet().EmitTxEvent(reader.GetCurrentReceipt().TxHash); err != nil {
log.Error("failed to load receipt", "err", err)
}
txHash := ttypes.Tx(txBytes).Hash()
// If a receipt was generated for an EVM tx add a link between the TM tx hash and the EVM tx hash
// so that we can use it to lookup relevant events using the TM tx hash.
if !bytes.Equal(txHash, receiptTxHash) {
a.childTxRefs = append(a.childTxRefs, evmaux.ChildTxRef{
ParentTxHash: txHash,
ChildTxHash: receiptTxHash,
})
}
receiptHandler.CommitCurrentReceipt()
}
Expand All @@ -704,13 +714,15 @@ func (a *Application) Commit() abci.ResponseCommit {
lvs := []string{"method", "Commit", "error", fmt.Sprint(err != nil)}
committedBlockCount.With(lvs...).Add(1)
commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds())
log.Info(fmt.Sprintf("commit took %f seconds-----\n", time.Since(begin).Seconds())) //todo we can remove these once performance comes back to normal state
}(time.Now())
appHash, _, err := a.Store.SaveVersion()
if err != nil {
panic(err)
}

a.EvmAuxStore.SaveChildTxRefs(a.childTxRefs)
a.childTxRefs = nil

height := a.curBlockHeader.GetHeight()
go func(height int64, blockHeader abci.Header) {
if err := a.EventHandler.EmitBlockTx(uint64(height), blockHeader.Time); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestSignatureTxMiddlewareMultipleTxSameBlock(t *testing.T) {
ctx2 := context.WithValue(context.Background(), ContextKeyOrigin, origin)
kvStore2 := store.NewMemStore()
state2 := loomchain.NewStoreState(ctx2, kvStore2, abci.Header{Height: 27}, nil, nil).WithOnChainConfig(cfg)
ctx2 = context.WithValue(ctx2, ContextKeyCheckTx, true)
_ = context.WithValue(ctx2, ContextKeyCheckTx, true)

//If we get the same sequence number in same block we should get an error
_, err = NonceTxHandler.Nonce(state2, kvStore2, nonceTxBytes,
Expand All @@ -93,7 +93,7 @@ func TestSignatureTxMiddlewareMultipleTxSameBlock(t *testing.T) {
ctx3 := context.WithValue(context.Background(), ContextKeyOrigin, origin)
kvStore3 := store.NewMemStore()
state3 := loomchain.NewStoreState(ctx3, kvStore3, abci.Header{Height: 27}, nil, nil).WithOnChainConfig(cfg)
ctx3 = context.WithValue(ctx3, ContextKeyCheckTx, true)
_ = context.WithValue(ctx3, ContextKeyCheckTx, true)

//If we get to tx with incrementing sequence numbers we should be fine in the same block
_, err = NonceTxHandler.Nonce(state3, kvStore3, nonceTxBytes2,
Expand All @@ -108,7 +108,7 @@ func TestSignatureTxMiddlewareMultipleTxSameBlock(t *testing.T) {
ctx3Dx := context.WithValue(context.Background(), ContextKeyOrigin, origin)
kvStore3Dx := store.NewMemStore()
state3Dx := loomchain.NewStoreState(ctx3Dx, kvStore3Dx, abci.Header{Height: 27}, nil, nil).WithOnChainConfig(cfg)
ctx3Dx = context.WithValue(ctx3Dx, ContextKeyCheckTx, true)
_ = context.WithValue(ctx3Dx, ContextKeyCheckTx, true)

_, err = NonceTxHandler.Nonce(state3Dx, kvStore3Dx, nonceTxBytes,
func(state3 loomchain.State, txBytes []byte, isCheckTx bool) (loomchain.TxHandlerResult, error) {
Expand Down
1 change: 1 addition & 0 deletions auth/multi_chain_sigtx_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var (

func TestSigning(t *testing.T) {
pk, err := crypto.GenerateKey()
require.NoError(t, err)
err = crypto.SaveECDSA("newpk", pk)
require.NoError(t, err)

Expand Down
85 changes: 85 additions & 0 deletions builtin/plugins/sample_go_contract/evm_sample_go_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// +build evm

package sample_go_contract

import (
"errors"
"io/ioutil"
"math/big"
"path"
"runtime"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/loomnetwork/go-loom"
types "github.com/loomnetwork/go-loom/builtin/types/sample_go_contract"
"github.com/loomnetwork/go-loom/plugin/contractpb"
)

const (
innerEmitterAbiFilename = "./testdata/InnerEmitter.abi"
outerEmitterAbiFilename = "./testdata/OuterEmitter.abi"
)

func (k *SampleGoContract) TestNestedEvmCalls(ctx contractpb.Context, req *types.SampleGoContractNestedEvmRequest) error {
if err := testInnerEmitter(ctx, loom.UnmarshalAddressPB(req.InnerEmitter), req.InnerEmitterValue); err != nil {
return err
}
if err := testOuterEmitter(ctx, loom.UnmarshalAddressPB(req.OuterEmitter), req.OuterEmitterValue); err != nil {
return err
}
return nil
}

func testInnerEmitter(ctx contractpb.Context, addr loom.Address, value uint64) error {
_, filename, _, ok := runtime.Caller(1)
if !ok {
return errors.New("cannot find the path of evm_sample_go_contract.go")
}
abiPath := path.Join(path.Dir(filename), innerEmitterAbiFilename)
abiBytes, err := ioutil.ReadFile(abiPath)
if err != nil {
return err
}
abiEventData, err := abi.JSON(strings.NewReader(string(abiBytes)))
if err != nil {
return err
}
input, err := abiEventData.Pack("sendEvent", big.NewInt(int64(value)))
if err != nil {
return err
}
evmOut := []byte{}
err = contractpb.CallEVM(ctx, addr, input, &evmOut)
if err != nil {
return err
}
return nil
}

func testOuterEmitter(ctx contractpb.Context, addr loom.Address, value uint64) error {
_, filename, _, ok := runtime.Caller(1)
if !ok {
return errors.New("cannot find the path of evm_sample_go_contract.go")
}
abiPath := path.Join(path.Dir(filename), outerEmitterAbiFilename)
abiBytes, err := ioutil.ReadFile(abiPath)
if err != nil {
return err
}
abiEventData, err := abi.JSON(strings.NewReader(string(abiBytes)))
if err != nil {
return err
}
input, err := abiEventData.Pack("sendEvent", big.NewInt(int64(value)))
if err != nil {
return err
}
evmOut := []byte{}

err = contractpb.CallEVM(ctx, addr, input, &evmOut)
if err != nil {
return err
}
return nil
}
13 changes: 13 additions & 0 deletions builtin/plugins/sample_go_contract/noevm_sample_go_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// +build !evm

package sample_go_contract

import (
types "github.com/loomnetwork/go-loom/builtin/types/sample_go_contract"
"github.com/loomnetwork/go-loom/plugin/contractpb"
"github.com/pkg/errors"
)

func (k *SampleGoContract) TestNestedEvmCalls(_ contractpb.Context, _ *types.SampleGoContractNestedEvmRequest) error {
return errors.New("testing evm in non evm build")
}
23 changes: 23 additions & 0 deletions builtin/plugins/sample_go_contract/sample_go_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package sample_go_contract

import (
types "github.com/loomnetwork/go-loom/builtin/types/sample_go_contract"
"github.com/loomnetwork/go-loom/plugin"
"github.com/loomnetwork/go-loom/plugin/contractpb"
)

type SampleGoContract struct {
}

func (k *SampleGoContract) Meta() (plugin.Meta, error) {
return plugin.Meta{
Name: "sample-go-contract",
Version: "1.0.0",
}, nil
}

func (k *SampleGoContract) Init(ctx contractpb.Context, req *types.SampleGoContractInitRequest) error {
return nil
}

var Contract plugin.Contract = contractpb.MakePluginContract(&SampleGoContract{})
69 changes: 69 additions & 0 deletions builtin/plugins/sample_go_contract/sample_go_contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// +build evm

package sample_go_contract

import (
"encoding/hex"
"io/ioutil"
"testing"

"github.com/loomnetwork/go-loom"
types "github.com/loomnetwork/go-loom/builtin/types/sample_go_contract"
"github.com/loomnetwork/go-loom/plugin/contractpb"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"

"github.com/loomnetwork/loomchain/evm"
"github.com/loomnetwork/loomchain/features"
"github.com/loomnetwork/loomchain/plugin"
)

var (
addr1 = loom.MustParseAddress("chain:0xb16a379ec18d4093666f8f38b11a3071c920207d")
caller = loom.MustParseAddress("chain:0x5cecd1f7261e1f4c684e297be3edf03b825e01c4")
)

func TestSampleGoContract(t *testing.T) {
pctx := plugin.CreateFakeContextWithEVM(caller, addr1)

sampleGoContract := &SampleGoContract{}
sampleAddr := pctx.CreateContract(Contract)
ctx := contractpb.WrapPluginContext(pctx.WithAddress(sampleAddr))
sampleInit := types.SampleGoContractInitRequest{}
require.NoError(t, sampleGoContract.Init(ctx, &sampleInit))

pctx.State.SetFeature(features.EvmConstantinopleFeature, true)

testInnerEmitterAddr, err := deployContractToEVM(pctx, "InnerEmitter", caller)
require.NoError(t, err)

testChainEventAddr, err := deployContractToEVM(pctx, "OuterEmitter", caller)
require.NoError(t, err)

req := types.SampleGoContractNestedEvmRequest{
InnerEmitter: testInnerEmitterAddr.MarshalPB(),
OuterEmitter: testChainEventAddr.MarshalPB(),
InnerEmitterValue: 7,
OuterEmitterValue: 77,
}
require.NoError(t, sampleGoContract.TestNestedEvmCalls(ctx, &req))
}

func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller loom.Address) (loom.Address, error) {
contractAddr := loom.Address{}
hexByteCode, err := ioutil.ReadFile("testdata/" + filename + ".bin")
if err != nil {
return contractAddr, err
}
byteCode := common.FromHex(string(hexByteCode))
byteCode, err = hex.DecodeString(string(hexByteCode))

vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false)
_, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0))
if err != nil {
return contractAddr, err
}
ctx.RegisterContract("", contractAddr, caller)
return contractAddr, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"i","type":"uint256"}],"name":"sendEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"number","type":"uint256"}],"name":"MyEvent","type":"event"}]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600f57600080fd5b5060b48061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d0a2d2cb14602d575b600080fd5b605660048036036020811015604157600080fd5b81019080803590602001909291905050506058565b005b807f6c2b4666ba8da5a95717621d879a77de725f3d816709b9cbe9f059b8f875e28460405160405180910390a25056fea165627a7a7230582002f672efd8c6b210ffa70c9cceed43480269dfbcb9cf29a770bf48fa78c845750029
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"i","type":"uint256"}],"name":"sendEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561001057600080fd5b50610204806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063d0a2d2cb14610030575b600080fd5b61005c6004803603602081101561004657600080fd5b810190808035906020019092919050505061005e565b005b600060405161006c906100fa565b604051809103906000f080158015610088573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663d0a2d2cb836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156100de57600080fd5b505af11580156100f2573d6000803e3d6000fd5b505050505050565b60d2806101078339019056fe6080604052348015600f57600080fd5b5060b48061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d0a2d2cb14602d575b600080fd5b605660048036036020811015604157600080fd5b81019080803590602001909291905050506058565b005b807f6c2b4666ba8da5a95717621d879a77de725f3d816709b9cbe9f059b8f875e28460405160405180910390a25056fea165627a7a723058207fec2eea969df7b0ecdbb68d123dd1b9365b066f11039c266ec1a86e843e53680029a165627a7a723058209feda54308151c758353c3137751f94d38f3daebc2c2b1278480fa5d25e05eb10029
4 changes: 4 additions & 0 deletions cmd/loom/common/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/loomnetwork/loomchain/builtin/plugins/ethcoin"
"github.com/loomnetwork/loomchain/builtin/plugins/karma"
"github.com/loomnetwork/loomchain/builtin/plugins/plasma_cash"
"github.com/loomnetwork/loomchain/builtin/plugins/sample_go_contract"
"github.com/loomnetwork/loomchain/builtin/plugins/user_deployer_whitelist"
"github.com/loomnetwork/loomchain/cmd/loom/replay"
"github.com/loomnetwork/loomchain/config"
Expand Down Expand Up @@ -37,6 +38,9 @@ func NewDefaultContractsLoader(cfg *config.Config) plugin.Loader {
if cfg.TransferGateway.ContractEnabled {
contracts = append(contracts, ethcoin.Contract)
}
if cfg.SampleGoContractEnabled {
contracts = append(contracts, sample_go_contract.Contract)
}
if cfg.ChainConfig.ContractEnabled {
contracts = append(contracts, chainconfig.Contract)
}
Expand Down
11 changes: 11 additions & 0 deletions cmd/loom/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ func defaultGenesis(cfg *config.Config, validator *loom.Validator) (*config.Gene
})
}

if cfg.SampleGoContractEnabled {
contracts = append(contracts,
config.ContractConfig{
VMTypeName: "plugin",
Format: "plugin",
Name: "sample-go-contract",
Location: "sample-go-contract:1.0.0",
},
)
}

if cfg.TransferGateway.ContractEnabled {
contracts = append(contracts,
config.ContractConfig{
Expand Down
Loading

0 comments on commit 58da756

Please sign in to comment.