diff --git a/Makefile b/Makefile index 22be3fe768..b901fb5ff8 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ BINANCE_TGORACLE_DIR=$(GOPATH)/src/$(PKG_BINANCE_TGORACLE) # NOTE: To build on Jenkins using a custom go-loom branch update the `deps` target below to checkout # that branch, you only need to update GO_LOOM_GIT_REV if you wish to lock the build to a # specific commit. -GO_LOOM_GIT_REV = HEAD +GO_LOOM_GIT_REV = tx-router-cfg # Specifies the loomnetwork/transfer-gateway branch/revision to use. TG_GIT_REV = HEAD # loomnetwork/go-ethereum loomchain branch diff --git a/cmd/loom/handler_test.go b/cmd/loom/handler_test.go index 1582220d6b..3a69238560 100644 --- a/cmd/loom/handler_test.go +++ b/cmd/loom/handler_test.go @@ -7,6 +7,7 @@ import ( proto "github.com/gogo/protobuf/proto" loom "github.com/loomnetwork/go-loom" lauth "github.com/loomnetwork/go-loom/auth" + cctypes "github.com/loomnetwork/go-loom/builtin/types/chainconfig" "github.com/loomnetwork/go-loom/types" "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/auth" @@ -65,6 +66,57 @@ func TestTxHandlerWithInvalidCaller(t *testing.T) { require.True(t, strings.HasPrefix(err.Error(), "Origin doesn't match caller")) } +func TestSingleRouteTxHandlerWithInvalidCaller(t *testing.T) { + _, alicePrivKey, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + bobPubKey, _, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) + require.NoError(t, err) + + vmManager := vm.NewManager() + router := loomchain.NewTxRouter() + router.Handle(1, &vm.DeployTxHandler{Manager: vmManager, CreateRegistry: createRegistry}) + router.Handle(2, &vm.CallTxHandler{Manager: vmManager}) + + kvStore := store.NewMemStore() + state := loomchain.NewStoreState(nil, kvStore, abci.Header{ChainID: "default"}, nil, nil) + require.NoError(t, store.SaveOnChainConfig(kvStore, &cctypes.Config{ + TxRouter: &cctypes.TxRouterConfig{ + UseSingleRoute: true, + }, + })) + + txMiddleWare := []loomchain.TxMiddleware{ + auth.SignatureTxMiddleware, + auth.NewNonceHandler().TxMiddleware(kvStore), + } + + rootHandler := loomchain.MiddlewareTxHandler(txMiddleWare, router, nil) + signer := lauth.NewEd25519Signer(alicePrivKey) + caller := loom.Address{ + ChainID: "default", + Local: loom.LocalAddressFromPublicKey(bobPubKey), + } + + // Try to process txs in which Alice attempts to impersonate Bob + _, err = rootHandler.ProcessTx(state, createTxWithInvalidCaller(t, signer, caller, &vm.DeployTx{ + VmType: vm.VMType_PLUGIN, + Code: nil, + Name: "hello", + }, 1, 1), false) + require.Error(t, err) + require.True(t, strings.HasPrefix(err.Error(), "Origin doesn't match caller")) + + _, err = rootHandler.ProcessTx(state, createTxWithInvalidCaller(t, signer, caller, &vm.CallTx{ + VmType: vm.VMType_PLUGIN, + }, 2, 2), false) + require.Error(t, err) + require.True(t, strings.HasPrefix(err.Error(), "Origin doesn't match caller")) +} + func createTxWithInvalidCaller(t *testing.T, signer lauth.Signer, caller loom.Address, tx proto.Message, txType uint32, nonce uint64) []byte { payload, err := proto.Marshal(tx) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 8b730ee3e7..1d1b852d36 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -879,6 +879,8 @@ func loadApp( router := loomchain.NewTxRouter() + // legacy router setup + isEvmTx := func(txID uint32, state loomchain.State, txBytes []byte, isCheckTx bool) bool { var msg vm.MessageTx err := proto.Unmarshal(txBytes, &msg) @@ -921,6 +923,18 @@ func loadApp( router.HandleCheckTx(2, loomchain.GenerateConditionalRouteHandler(isEvmTx, loomchain.NoopTxHandler, callTxHandler)) router.HandleCheckTx(3, loomchain.GenerateConditionalRouteHandler(isEvmTx, loomchain.NoopTxHandler, migrationTxHandler)) + // non-legacy router setup + + router.Handle(1, &tx_handler.DeployTxHandler{ + Manager: vmManager, + CreateRegistry: createRegistry, + AllowNamedEVMContracts: cfg.AllowNamedEvmContracts, + }) + router.Handle(2, &tx_handler.CallTxHandler{ + Manager: vmManager, + }) + router.Handle(3, migrationTxHandler) + txMiddleWare := []loomchain.TxMiddleware{ loomchain.LogTxMiddleware, loomchain.RecoveryTxMiddleware, diff --git a/router.go b/router.go index 271ad41042..2b8172dd9a 100644 --- a/router.go +++ b/router.go @@ -9,6 +9,8 @@ import ( type Transaction = types.Transaction type TxRouter struct { + routes map[uint32]RouteHandler + // legacy, will be removed in a future release deliverTxRoutes map[uint32]RouteHandler checkTxRoutes map[uint32]RouteHandler } @@ -34,11 +36,20 @@ func GenerateConditionalRouteHandler(conditionFn RouteConditionFunc, onTrue TxHa func NewTxRouter() *TxRouter { return &TxRouter{ + routes: make(map[uint32]RouteHandler), deliverTxRoutes: make(map[uint32]RouteHandler), checkTxRoutes: make(map[uint32]RouteHandler), } } +func (r *TxRouter) Handle(txID uint32, handler TxHandler) { + if _, ok := r.routes[txID]; ok { + panic("handler for transaction already registered") + } + // TODO: remove the GeneratePassthroughRouteHandler once the deliver/checkTxRoutes are gone + r.routes[txID] = GeneratePassthroughRouteHandler(handler) +} + func (r *TxRouter) HandleDeliverTx(txID uint32, handler RouteHandler) { if _, ok := r.deliverTxRoutes[txID]; ok { panic("handler for transaction already registered") @@ -65,7 +76,10 @@ func (r *TxRouter) ProcessTx(state State, txBytes []byte, isCheckTx bool) (TxHan } var routeHandler RouteHandler - if isCheckTx { + + if state.Config().GetTxRouter().UseSingleRoute { + routeHandler = r.routes[tx.Id] + } else if isCheckTx { routeHandler = r.checkTxRoutes[tx.Id] } else { routeHandler = r.deliverTxRoutes[tx.Id] diff --git a/tx_handler/call_tx_handler.go b/tx_handler/call_tx_handler.go new file mode 100644 index 0000000000..f0f56a69e8 --- /dev/null +++ b/tx_handler/call_tx_handler.go @@ -0,0 +1,75 @@ +package tx_handler + +import ( + "fmt" + + "github.com/gogo/protobuf/proto" + "github.com/loomnetwork/go-loom" + "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/auth" + "github.com/loomnetwork/loomchain/eth/utils" + "github.com/loomnetwork/loomchain/vm" + "github.com/pkg/errors" +) + +// CallTxHandler handles txs that call Go & EVM contracts +type CallTxHandler struct { + *vm.Manager +} + +func (h *CallTxHandler) ProcessTx( + state loomchain.State, + txBytes []byte, + isCheckTx bool, +) (loomchain.TxHandlerResult, error) { + var r loomchain.TxHandlerResult + + var msg vm.MessageTx + if err := proto.Unmarshal(txBytes, &msg); err != nil { + return r, err + } + + origin := auth.Origin(state.Context()) + caller := loom.UnmarshalAddressPB(msg.From) + addr := loom.UnmarshalAddressPB(msg.To) + + if caller.Compare(origin) != 0 { + return r, fmt.Errorf("Origin doesn't match caller: %v != %v", origin, caller) + } + + // TODO: move the marshalling & validation above this line into middleware + var tx vm.CallTx + if err := proto.Unmarshal(msg.Data, &tx); err != nil { + return r, err + } + + switch tx.VmType { + case vm.VMType_EVM: + r.Info = utils.CallEVM + // Only do basic validation of EVM calls in CheckTx, don't execute the actual call + if isCheckTx { + return r, nil + } + + case vm.VMType_PLUGIN: + r.Info = utils.CallPlugin + + default: + return r, errors.New("invalid vm type") + } + + vmInstance, err := h.Manager.InitVM(tx.VmType, state) + if err != nil { + return r, err + } + + var value *loom.BigUInt + if tx.Value == nil { + value = loom.NewBigUIntFromInt(0) + } else { + value = &tx.Value.Value + } + + r.Data, err = vmInstance.Call(origin, addr, tx.Input, value) + return r, err +} diff --git a/tx_handler/deploy_tx_handler.go b/tx_handler/deploy_tx_handler.go new file mode 100644 index 0000000000..b6a7a6924b --- /dev/null +++ b/tx_handler/deploy_tx_handler.go @@ -0,0 +1,103 @@ +package tx_handler + +import ( + "fmt" + + "github.com/gogo/protobuf/proto" + "github.com/loomnetwork/go-loom" + "github.com/loomnetwork/go-loom/types" + "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/auth" + "github.com/loomnetwork/loomchain/eth/utils" + registry "github.com/loomnetwork/loomchain/registry/factory" + "github.com/loomnetwork/loomchain/vm" + "github.com/pkg/errors" +) + +// DeployTxHandler handles txs that deploy Go & EVM contracts +type DeployTxHandler struct { + *vm.Manager + CreateRegistry registry.RegistryFactoryFunc + AllowNamedEVMContracts bool +} + +func (h *DeployTxHandler) ProcessTx( + state loomchain.State, + txBytes []byte, + isCheckTx bool, +) (loomchain.TxHandlerResult, error) { + var r loomchain.TxHandlerResult + + var msg vm.MessageTx + if err := proto.Unmarshal(txBytes, &msg); err != nil { + return r, err + } + + origin := auth.Origin(state.Context()) + caller := loom.UnmarshalAddressPB(msg.From) + + if caller.Compare(origin) != 0 { + return r, fmt.Errorf("Origin doesn't match caller: - %v != %v", origin, caller) + } + + // TODO: move the marshalling & validation above this line into middleware + var tx vm.DeployTx + if err := proto.Unmarshal(msg.Data, &tx); err != nil { + return r, err + } + + switch tx.VmType { + case vm.VMType_EVM: + r.Info = utils.DeployEvm + + if (len(tx.Name) > 0) && !h.AllowNamedEVMContracts { + return r, errors.New("named evm contracts are not allowed") + } + + // Only do basic validation of EVM deploys in CheckTx, don't execute the actual deploy + if isCheckTx { + return r, nil + } + + case vm.VMType_PLUGIN: + r.Info = utils.DeployPlugin + + default: + return r, errors.New("invalid vm type") + } + + vmInstance, err := h.Manager.InitVM(tx.VmType, state) + if err != nil { + return r, err + } + + var value *loom.BigUInt + if tx.Value == nil { + value = loom.NewBigUIntFromInt(0) + } else { + value = &tx.Value.Value + } + + retCreate, addr, err := vmInstance.Create(origin, tx.Code, value) + if err != nil { + return r, errors.Wrapf(err, "failed to create contract") + } + + response, err := proto.Marshal(&vm.DeployResponse{ + Contract: &types.Address{ + ChainId: addr.ChainID, + Local: addr.Local, + }, + Output: retCreate, + }) + if err != nil { + return r, errors.Wrapf(err, "failed to marshal deploy response") + } + r.Data = response + + reg := h.CreateRegistry(state) + if err := reg.Register(tx.Name, addr, caller); err != nil { + return r, err + } + return r, nil +}