Repository: tablelandnetwork/go-tableland
Pull request #190 (2022-07-19 19:52:55)

Fixes transfer event execution
# Summary

The execution of a transfer event was not changing the table's owner in the registry table.

# Context

[Two tables were transferred on Goerli](https://goerli.etherscan.io/address/0x0C53611b433B486ec940334653F7756D75147dEA#tokentxnsErc721) (57 and 59) to address `0x0C53611b433B486ec940334653F7756D75147dEA`, but when executing the call `https://testnet.tableland.network/chain/5/tables/controller/0x0C53611b433B486ec940334653F7756D75147dEA` you could not see those tables listed.

The undesired behavior happened because of a missing logic when executing the transfer event logic. 

## More info

- I already synced from scratch locally (pointing to `testnet` contract) and the logic worked. 
- Deployed to staging
- Even after this PR is merged and deployed to `testnet`, we'll need to sync all events from scratch to pick up the change (that will probably be done in a future time)


cc @sanderpick 


Patch:
diff --git a/pkg/eventprocessor/impl/eventprocessor.go b/pkg/eventprocessor/impl/eventprocessor.go
index 2d863ea..d2f7e70 100644
--- a/pkg/eventprocessor/impl/eventprocessor.go
+++ b/pkg/eventprocessor/impl/eventprocessor.go
@@ -376,9 +376,9 @@ func (ep *EventProcessor) executeCreateTableEvent(
 	tableID := tableland.TableID(*e.TableId)
 
 	if err := b.InsertTable(ctx, tableID, e.Owner.Hex(), createStmt); err != nil {
-		var pgErr *txn.ErrQueryExecution
-		if errors.As(err, &pgErr) {
-			err := fmt.Sprintf("table creation execution failed (code: %s, msg: %s)", pgErr.Code, pgErr.Msg)
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("table creation execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
 			receipt.Error = &err
 			return receipt, nil
 		}
@@ -418,9 +418,9 @@ func (ep *EventProcessor) executeRunSQLEvent(
 		return receipt, nil
 	}
 	if err := b.ExecWriteQueries(ctx, e.Caller, mutatingStmts, e.IsOwner, &policy{e.Policy}); err != nil {
-		var pgErr *txn.ErrQueryExecution
-		if errors.As(err, &pgErr) {
-			err := fmt.Sprintf("db query execution failed (code: %s, msg: %s)", pgErr.Code, pgErr.Msg)
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("db query execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
 			receipt.Error = &err
 			return receipt, nil
 		}
@@ -454,9 +454,9 @@ func (ep *EventProcessor) executeSetControllerEvent(
 	tableID := tableland.TableID(*e.TableId)
 
 	if err := b.SetController(ctx, tableID, e.Controller); err != nil {
-		var pgErr *txn.ErrQueryExecution
-		if errors.As(err, &pgErr) {
-			err := fmt.Sprintf("set controller execution failed (code: %s, msg: %s)", pgErr.Code, pgErr.Msg)
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("set controller execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
 			receipt.Error = &err
 			return receipt, nil
 		}
@@ -487,22 +487,32 @@ func (ep *EventProcessor) executeTransferEvent(
 		receipt.Error = &tableIDIsEmpty
 		return receipt, nil
 	}
+
 	tableID := tableland.TableID(*e.TableId)
+	if err := b.ChangeTableOwner(ctx, tableID, e.To); err != nil {
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("change table owner execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
+			receipt.Error = &err
+			return receipt, nil
+		}
+		return eventprocessor.Receipt{}, fmt.Errorf("executing change table owner: %s", err)
+	}
 
 	privileges := tableland.Privileges{tableland.PrivInsert, tableland.PrivUpdate, tableland.PrivDelete}
 	if err := b.RevokePrivileges(ctx, tableID, e.From, privileges); err != nil {
-		var pgErr *txn.ErrQueryExecution
-		if errors.As(err, &pgErr) {
-			err := fmt.Sprintf("revoke privileges execution failed (code: %s, msg: %s)", pgErr.Code, pgErr.Msg)
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("revoke privileges execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
 			receipt.Error = &err
 			return receipt, nil
 		}
 		return eventprocessor.Receipt{}, fmt.Errorf("executing revoke privileges: %s", err)
 	}
 	if err := b.GrantPrivileges(ctx, tableID, e.To, privileges); err != nil {
-		var pgErr *txn.ErrQueryExecution
-		if errors.As(err, &pgErr) {
-			err := fmt.Sprintf("grant privileges execution failed (code: %s, msg: %s)", pgErr.Code, pgErr.Msg)
+		var dbErr *txn.ErrQueryExecution
+		if errors.As(err, &dbErr) {
+			err := fmt.Sprintf("grant privileges execution failed (code: %s, msg: %s)", dbErr.Code, dbErr.Msg)
 			receipt.Error = &err
 			return receipt, nil
 		}
diff --git a/pkg/txn/impl/processor.go b/pkg/txn/impl/processor.go
index 7aed79c..f96effc 100644
--- a/pkg/txn/impl/processor.go
+++ b/pkg/txn/impl/processor.go
@@ -420,6 +420,38 @@ func (b *batch) TxnReceiptExists(ctx context.Context, txnHash common.Hash) (bool
 	return exists, nil
 }
 
+// ChangeTableOwner changes the owner of the table in the registry table.
+func (b *batch) ChangeTableOwner(
+	ctx context.Context,
+	id tableland.TableID,
+	newOwner common.Address,
+) error {
+	f := func(ctx context.Context, tx *sql.Tx) error {
+		if _, err := tx.ExecContext(ctx,
+			`UPDATE registry SET controller = ?1 WHERE id = ?2 AND chain_id = ?3;`,
+			newOwner.Hex(),
+			id.String(),
+			b.tp.chainID,
+		); err != nil {
+			if code, ok := isErrCausedByQuery(err); ok {
+				return &txn.ErrQueryExecution{
+					Code: "SQLITE_" + code,
+					Msg:  err.Error(),
+				}
+			}
+			return fmt.Errorf("updating table owner: %s", err)
+		}
+
+		return nil
+	}
+
+	if err := runWithinSavepoint(ctx, b.txn, f); err != nil {
+		return fmt.Errorf("running within savepoint: %w", err)
+	}
+
+	return nil
+}
+
 // Close closes gracefully the batch. Clients should *always* `defer Close()` when
 // opening batches.
 func (b *batch) Close() error {
diff --git a/pkg/txn/txn.go b/pkg/txn/txn.go
index b8c2dd2..6d0c885 100644
--- a/pkg/txn/txn.go
+++ b/pkg/txn/txn.go
@@ -66,6 +66,8 @@ type Batch interface {
 	SaveTxnReceipts(ctx context.Context, rs []eventprocessor.Receipt) error
 	TxnReceiptExists(ctx context.Context, txnHash common.Hash) (bool, error)
 
+	ChangeTableOwner(ctx context.Context, id tableland.TableID, newOwner common.Address) error
+
 	Commit() error
 	Close() error
 }


Test patch:
diff --git a/internal/tableland/impl/mesa_test.go b/internal/tableland/impl/mesa_test.go
index 89199ce..07aff9b 100644
--- a/internal/tableland/impl/mesa_test.go
+++ b/internal/tableland/impl/mesa_test.go
@@ -453,6 +453,19 @@ func TestTransferTable(t *testing.T) {
 		100*time.Millisecond,
 	)
 	requireReceipts(ctx, t, tbldOwner2, []string{r2.Transaction.Hash}, true)
+
+	// check registry table new ownership
+	require.Eventually(t,
+		runSQLCountEq(ctx,
+			t,
+			tbldOwner2,
+			fmt.Sprintf("SELECT * FROM registry WHERE controller = '%s' AND id = 1 AND chain_id = 1337", txOptsOwner2.From.Hex()), // nolint
+			txOptsOwner2.From.Hex(),
+			1,
+		),
+		5*time.Second,
+		100*time.Millisecond,
+	)
 }
 
 func TestQueryConstraints(t *testing.T) {
diff --git a/pkg/txn/impl/processor_test.go b/pkg/txn/impl/processor_test.go
index ecd7580..f0aa31b 100644
--- a/pkg/txn/impl/processor_test.go
+++ b/pkg/txn/impl/processor_test.go
@@ -22,8 +22,10 @@ import (
 
 // Random address for testing. The value isn't important
 // because the ACL is mocked.
-var controller = common.HexToAddress("0x07dfFc57AA386D2b239CaBE8993358DF20BAFBE2")
-var chainID = 1337
+var (
+	controller = common.HexToAddress("0x07dfFc57AA386D2b239CaBE8993358DF20BAFBE2")
+	chainID    = 1337
+)
 
 func TestExecWriteQueries(t *testing.T) {
 	t.Parallel()
@@ -804,6 +806,48 @@ func TestWithCheck(t *testing.T) {
 	})
 }
 
+func TestChangeTableOwner(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+
+	tableID := tableland.TableID(*big.NewInt(100))
+	txnp, _, db := newTxnProcessorWithTable(t, 0)
+
+	require.Equal(t, 1,
+		tableRowCountT100(
+			t,
+			db,
+			fmt.Sprintf(
+				"select count(1) from registry WHERE controller = '%s' and id = %s and chain_id = %d",
+				"0xb451cee4A42A652Fe77d373BAe66D42fd6B8D8FF",
+				tableID.String(),
+				chainID,
+			),
+		))
+
+	b, err := txnp.OpenBatch(ctx)
+	require.NoError(t, err)
+
+	// change table's owner
+	err = b.ChangeTableOwner(ctx, tableID, controller)
+	require.NoError(t, err)
+	require.NoError(t, b.Commit())
+	require.NoError(t, b.Close())
+	require.NoError(t, txnp.Close(ctx))
+
+	require.Equal(t, 1,
+		tableRowCountT100(
+			t,
+			db,
+			fmt.Sprintf(
+				"select count(1) from registry WHERE controller = '%s' and id = %s and chain_id = %d",
+				controller.Hex(),
+				tableID.String(),
+				chainID,
+			),
+		))
+}
+
 func tableRowCountT100(t *testing.T, pool *sql.DB, query string) int {
 	t.Helper()