Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

noto: add "lock" functionality #483

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d7f57bb
Add SendTransaction to domain callbacks
awrichar Dec 20, 2024
1cfbc66
Add LocalNodeName query from domains
awrichar Dec 20, 2024
9959bb3
zeto: fix unit test
awrichar Jan 16, 2025
8ff2532
Add test coverage for new domain methods
awrichar Jan 16, 2025
70fa7bc
Add GetStates to query domain states by ID
awrichar Jan 7, 2025
ec4024b
Add test coverage for GetStates
awrichar Jan 16, 2025
a413a80
noto: remove Noto SelfSubmit variant
awrichar Dec 14, 2024
1a77010
noto: add beginnings of "lock" functionality
awrichar Dec 2, 2024
5b35ede
noto: finish implementation of basic lock/unlock behavior
awrichar Dec 17, 2024
e9b78ce
noto: allow specifying many lock outcomes at a time
awrichar Dec 17, 2024
73ea6e5
noto: update Solidity comments
awrichar Dec 17, 2024
880d8ad
noto: allow locks to have any number of outcomes
awrichar Dec 18, 2024
2890619
noto: check mint/burn policy again at endorsement
awrichar Dec 18, 2024
e77a35d
noto: shorten some config names
awrichar Dec 18, 2024
f52aba1
noto: remove unused verifier lookups in assemble
awrichar Dec 18, 2024
8a65f82
noto: add updateLock to add new recipients to an existing lock
awrichar Dec 18, 2024
ba2b212
noto: helper for preparing hook transactions
awrichar Dec 20, 2024
fcca179
noto: add lock/unlock support to hooks
awrichar Dec 23, 2024
91b85b2
sdk: add state queries
awrichar Dec 23, 2024
baef1e7
example: add Noto lock example
awrichar Dec 23, 2024
f9a93e5
noto: pass data to hooks
awrichar Dec 23, 2024
1c4f221
noto: simplify lock/unlock interface
awrichar Dec 24, 2024
1c3ea77
noto: add salt to locked coin schema
awrichar Dec 26, 2024
e9c78d8
noto: add prepareUnlock
awrichar Dec 26, 2024
1879346
noto: implement approveUnlock and unlockWithApproval
awrichar Dec 27, 2024
1b0fd0c
Fix up lock example
awrichar Dec 27, 2024
7a28b17
noto: assembly should revert for insufficient funds
awrichar Dec 27, 2024
659f17a
noto: fix up hook support for lock/unlock
awrichar Dec 27, 2024
e15200a
noto: begin adding some more unit tests for event handling
awrichar Dec 30, 2024
21baa86
Use Noto locks in Noto/Zeto PvP test
awrichar Dec 30, 2024
2f39d0a
noto: more unit tests for event handler
awrichar Dec 31, 2024
67cd72a
Remove unused atom helper
awrichar Jan 4, 2025
55e9708
noto: split config options by notary type
awrichar Jan 5, 2025
84ccbb1
noto: add required "notaryMode" parameter to constructor
awrichar Jan 5, 2025
5ec8a10
noto: group constructor options by notary mode
awrichar Jan 5, 2025
f0e1819
noto: add a specific flag to use public hooks
awrichar Jan 5, 2025
5edc8da
noto: add basic config to control lock and unlock
awrichar Jan 5, 2025
fe63992
noto: fix operator e2e tests and examples
awrichar Jan 5, 2025
03ad94b
noto: only store a hash of the prepared unlock operation
awrichar Jan 7, 2025
6a7b4ab
noto: switch some calls from "find available states" to "get states"
awrichar Jan 16, 2025
0e89d60
noto: clean up calls to hooks ABI
awrichar Jan 7, 2025
8618d69
Fix lock example for unlockWithApproval
awrichar Jan 8, 2025
3a57841
noto: replace approveUnlock with delegateLock
awrichar Jan 9, 2025
668233c
noto: combine "unlockWithApproval" into "unlock"
awrichar Jan 13, 2025
69b291a
noto: remove "gather coins" helper
awrichar Jan 13, 2025
04c3d47
noto: clean up unlock states
awrichar Jan 14, 2025
8188b80
noto: clean up unlock recipients
awrichar Jan 14, 2025
6c4bcfb
Clean up tracker contracts
awrichar Jan 15, 2025
dd3afc8
noto: remove "from" param from unlock hooks
awrichar Jan 15, 2025
5e85d53
noto: add domain receipt with states broken down by type
awrichar Jan 15, 2025
4c99cfe
noto: include lock info in domain receipt
awrichar Jan 16, 2025
3002004
noto: allow Paladin to choose the lock ID
awrichar Jan 16, 2025
d70c259
noto: clean up inheritance of hooks contracts
awrichar Jan 17, 2025
951987d
noto: ensure mint/unlock are properly protected when using hooks
awrichar Jan 17, 2025
4aea3a8
noto: minor cleanup in smart contract
awrichar Jan 22, 2025
115d0e8
noto: add Solidity unit tests for lock/unlock
awrichar Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
**/*.pb.go
.git
**/.gradle

# Doc site is 100s of MB
doc-site/

# Zeto ZKP is 1GB
domains/zeto/zkp

# Other folders not needed for the Docker build
example/
sdk/

# The operator has its own docker build (with its own .dockerignore), so we don't want to rebuild the whole
# Paladin docker every time you're re-running the operator/test.
# So we only install enough to keep the build happy.
operator/**
!operator/go.mod
!operator/build.gradle
!operator/build.gradle
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
"type": "node",
"cwd": "${workspaceFolder}/example/bond"
},
{
"name": "Lock example: run",
"request": "launch",
"runtimeArgs": [
"run",
"start"
],
"runtimeExecutable": "npm",
"type": "node",
"cwd": "${workspaceFolder}/example/lock"
},
{
"name": "Run Controller",
"type": "go",
Expand Down
3 changes: 3 additions & 0 deletions core/go/internal/components/statemgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ type DomainContext interface {
// The dbTX is passed in to allow re-use of a connection during read operations.
FindAvailableStates(dbTX *gorm.DB, schemaID tktypes.Bytes32, query *query.QueryJSON) (Schema, []*pldapi.State, error)

// GetStates retrieves a set of states by ID
GetStates(dbTX *gorm.DB, schemaID tktypes.Bytes32, ids []string) (Schema, []*pldapi.State, error)

// Return a snapshot of all currently known state locks
ExportSnapshot() ([]byte, error)

Expand Down
90 changes: 73 additions & 17 deletions core/go/internal/domainmgr/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,26 @@ func (d *domain) Configuration() *prototk.DomainConfig {
return d.config
}

func toProtoStates(states []*pldapi.State) []*prototk.StoredState {
pbStates := make([]*prototk.StoredState, len(states))
for i, s := range states {
pbStates[i] = &prototk.StoredState{
Id: s.ID.String(),
SchemaId: s.Schema.String(),
CreatedAt: s.Created.UnixNano(),
DataJson: string(s.Data),
Locks: []*prototk.StateLock{},
}
for _, l := range s.Locks {
pbStates[i].Locks = append(pbStates[i].Locks, &prototk.StateLock{
Type: mapStateLockType(l.Type.V()),
Transaction: l.Transaction.String(),
})
}
}
return pbStates
}

// Domain callback to query the state store
func (d *domain) FindAvailableStates(ctx context.Context, req *prototk.FindAvailableStatesRequest) (*prototk.FindAvailableStatesResponse, error) {
c, err := d.checkInFlight(ctx, req.StateQueryContext)
Expand Down Expand Up @@ -306,24 +326,8 @@ func (d *domain) FindAvailableStates(ctx context.Context, req *prototk.FindAvail
return nil, err
}

pbStates := make([]*prototk.StoredState, len(states))
for i, s := range states {
pbStates[i] = &prototk.StoredState{
Id: s.ID.String(),
SchemaId: s.Schema.String(),
CreatedAt: s.Created.UnixNano(),
DataJson: string(s.Data),
Locks: []*prototk.StateLock{},
}
for _, l := range s.Locks {
pbStates[i].Locks = append(pbStates[i].Locks, &prototk.StateLock{
Type: mapStateLockType(l.Type.V()),
Transaction: l.Transaction.String(),
})
}
}
return &prototk.FindAvailableStatesResponse{
States: pbStates,
States: toProtoStates(states),
}, nil

}
Expand Down Expand Up @@ -771,3 +775,55 @@ func (d *domain) BuildDomainReceipt(ctx context.Context, dbTX *gorm.DB, txID uui
}
return tktypes.RawJSON(res.ReceiptJson), nil
}

func (d *domain) SendTransaction(ctx context.Context, tx *prototk.SendTransactionRequest) (*prototk.SendTransactionResponse, error) {
txType := pldapi.TransactionTypePrivate
if tx.Transaction.Type == prototk.TransactionInput_PUBLIC {
txType = pldapi.TransactionTypePublic
}
contractAddress, err := tktypes.ParseEthAddress(tx.Transaction.ContractAddress)
if err != nil {
return nil, err
}
var functionABI abi.Entry
if err = json.Unmarshal([]byte(tx.Transaction.FunctionAbiJson), &functionABI); err != nil {
return nil, err
}

id, err := d.dm.txManager.SendTransaction(ctx, &pldapi.TransactionInput{
TransactionBase: pldapi.TransactionBase{
Type: txType.Enum(),
From: tx.Transaction.From,
To: contractAddress,
Data: tktypes.RawJSON(tx.Transaction.ParamsJson),
},
ABI: abi.ABI{&functionABI},
})
if err != nil {
return nil, err
}
return &prototk.SendTransactionResponse{Id: id.String()}, nil
}

func (d *domain) LocalNodeName(ctx context.Context, req *prototk.LocalNodeNameRequest) (*prototk.LocalNodeNameResponse, error) {
return &prototk.LocalNodeNameResponse{
Name: d.dm.transportMgr.LocalNodeName(),
}, nil
}

func (d *domain) GetStates(ctx context.Context, req *prototk.GetStatesRequest) (*prototk.GetStatesResponse, error) {
c, err := d.checkInFlight(ctx, req.StateQueryContext)
if err != nil {
return nil, err
}

schemaID, err := tktypes.ParseBytes32(req.SchemaId)
if err != nil {
return nil, i18n.WrapError(ctx, err, msgs.MsgDomainInvalidSchemaID, req.SchemaId)
}

_, states, err := c.dCtx.GetStates(c.dbTX, schemaID, req.StateIds)
return &prototk.GetStatesResponse{
States: toProtoStates(states),
}, err
}
55 changes: 52 additions & 3 deletions core/go/internal/domainmgr/domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ func TestDomainInitStates(t *testing.T) {
}
func mockUpsertABIOk(mc *mockComponents) {
mc.txManager.On("UpsertABI", mock.Anything, mock.Anything, mock.Anything).Return(func() {}, &pldapi.StoredABI{
Hash: tktypes.Bytes32(tktypes.RandBytes(32)),
Hash: tktypes.RandBytes32(),
}, nil)
}

Expand Down Expand Up @@ -496,7 +496,7 @@ func TestDomainFindAvailableStatesFail(t *testing.T) {
})
defer done()

schemaID := tktypes.Bytes32(tktypes.RandBytes(32))
schemaID := tktypes.RandBytes32()
td.mdc.On("FindAvailableStates", mock.Anything, schemaID, mock.Anything).Return(nil, nil, fmt.Errorf("pop"))

assert.Nil(t, td.d.initError.Load())
Expand All @@ -510,7 +510,7 @@ func TestDomainFindAvailableStatesFail(t *testing.T) {

func storeTestState(t *testing.T, td *testDomainContext, txID uuid.UUID, amount *ethtypes.HexInteger) *fakeState {
state := &fakeState{
Salt: tktypes.Bytes32(tktypes.RandBytes(32)),
Salt: tktypes.RandBytes32(),
Owner: tktypes.EthAddress(tktypes.RandBytes(20)),
Amount: amount,
}
Expand Down Expand Up @@ -1022,6 +1022,55 @@ func TestRecoverSignerFailCases(t *testing.T) {
assert.Regexp(t, "PD011638", err)
}

func TestSendTransactionFailCases(t *testing.T) {
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas())
defer done()

_, err := td.d.SendTransaction(td.ctx, &prototk.SendTransactionRequest{
Transaction: &prototk.TransactionInput{
ContractAddress: "badnotgood",
FunctionAbiJson: `{}`,
ParamsJson: `{}`,
},
})
require.ErrorContains(t, err, "bad address")

_, err = td.d.SendTransaction(td.ctx, &prototk.SendTransactionRequest{
Transaction: &prototk.TransactionInput{
ContractAddress: "0x05d936207F04D81a85881b72A0D17854Ee8BE45A",
FunctionAbiJson: `bad`,
ParamsJson: `{}`,
},
})
require.ErrorContains(t, err, "invalid character")
}

func TestGetStatesFailCases(t *testing.T) {
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas())
defer done()

_, err := td.d.GetStates(td.ctx, &prototk.GetStatesRequest{
StateQueryContext: "bad",
})
require.ErrorContains(t, err, "PD011649")

_, err = td.d.GetStates(td.ctx, &prototk.GetStatesRequest{
StateQueryContext: td.c.id,
SchemaId: "bad",
})
require.ErrorContains(t, err, "PD011641")

schemaID := tktypes.Bytes32(tktypes.RandBytes(32))
td.mdc.On("GetStates", mock.Anything, schemaID, []string{"id1"}).Return(nil, nil, fmt.Errorf("pop"))

_, err = td.d.GetStates(td.ctx, &prototk.GetStatesRequest{
StateQueryContext: td.c.id,
SchemaId: schemaID.String(),
StateIds: []string{"id1"},
})
require.EqualError(t, err, "pop")
}

func TestMapStateLockType(t *testing.T) {
for _, pldType := range pldapi.StateLockType("").Options() {
assert.NotNil(t, mapStateLockType(pldapi.StateLockType(pldType)))
Expand Down
4 changes: 2 additions & 2 deletions core/go/internal/domainmgr/event_indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func TestHandleEventBatch(t *testing.T) {
stateConfirmed := tktypes.RandHex(32)
stateInfo := tktypes.RandHex(32)
fakeHash1 := tktypes.RandHex(32)
fakeSchema := tktypes.Bytes32(tktypes.RandBytes(32))
fakeSchema := tktypes.RandBytes32()
event1 := &pldapi.EventWithData{
Address: *contract1,
IndexedEvent: &pldapi.IndexedEvent{
Expand Down Expand Up @@ -439,7 +439,7 @@ func TestHandleEventBatchRegistrationError(t *testing.T) {
mp.Mock.ExpectExec("INSERT.*private_smart_contracts").WillReturnError(fmt.Errorf("pop"))

registrationData := &event_PaladinRegisterSmartContract_V0{
TXId: tktypes.Bytes32(tktypes.RandBytes(32)),
TXId: tktypes.RandBytes32(),
}
registrationDataJSON, err := json.Marshal(registrationData)
require.NoError(t, err)
Expand Down
53 changes: 49 additions & 4 deletions core/go/internal/domainmgr/private_smart_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,51 @@ func TestRecoverSignature(t *testing.T) {
assert.Equal(t, kp.Address.String(), res.Verifier)
}

func TestSendTransaction(t *testing.T) {
txID := uuid.New()
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas(), func(mc *mockComponents) {
mc.txManager.On("SendTransaction", mock.Anything, mock.Anything).Return(&txID, nil)
})
defer done()
assert.Nil(t, td.d.initError.Load())

_, err := td.d.SendTransaction(td.ctx, &prototk.SendTransactionRequest{
Transaction: &prototk.TransactionInput{
ContractAddress: "0x05d936207F04D81a85881b72A0D17854Ee8BE45A",
FunctionAbiJson: `{}`,
ParamsJson: `{}`,
},
})
require.NoError(t, err)
}

func TestSendTransactionFail(t *testing.T) {
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas(), func(mc *mockComponents) {
mc.txManager.On("SendTransaction", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
})
defer done()
assert.Nil(t, td.d.initError.Load())

_, err := td.d.SendTransaction(td.ctx, &prototk.SendTransactionRequest{
Transaction: &prototk.TransactionInput{
ContractAddress: "0x05d936207F04D81a85881b72A0D17854Ee8BE45A",
FunctionAbiJson: `{}`,
ParamsJson: `{}`,
},
})
require.EqualError(t, err, "pop")
}

func TestLocalNodeName(t *testing.T) {
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas())
defer done()
assert.Nil(t, td.d.initError.Load())

res, err := td.d.LocalNodeName(td.ctx, &prototk.LocalNodeNameRequest{})
require.NoError(t, err)
assert.Equal(t, "node1", res.Name)
}

func TestDomainInitTransactionMissingInput(t *testing.T) {
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas())
defer done()
Expand Down Expand Up @@ -606,13 +651,13 @@ func TestFullTransactionRealDBOK(t *testing.T) {
state4 := storeTestState(t, td, ptx.ID, ethtypes.NewHexInteger64(4444444))

state5 := &fakeState{
Salt: tktypes.Bytes32(tktypes.RandBytes(32)),
Salt: tktypes.RandBytes32(),
Owner: tktypes.EthAddress(tktypes.RandBytes(20)),
Amount: ethtypes.NewHexInteger64(5555555),
}

state6 := &fakeState{
Salt: tktypes.Bytes32(tktypes.RandBytes(32)),
Salt: tktypes.RandBytes32(),
Owner: tktypes.EthAddress(tktypes.RandBytes(20)),
Amount: ethtypes.NewHexInteger64(6666666),
}
Expand Down Expand Up @@ -957,7 +1002,7 @@ func TestDomainWritePotentialStatesBadSchema(t *testing.T) {

func TestDomainWritePotentialStatesFail(t *testing.T) {
schema := componentmocks.NewSchema(t)
schemaID := tktypes.Bytes32(tktypes.RandBytes(32))
schemaID := tktypes.RandBytes32()
schema.On("ID").Return(schemaID)
schema.On("Signature").Return("schema1_signature")
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas(schema), mockBlockHeight)
Expand All @@ -975,7 +1020,7 @@ func TestDomainWritePotentialStatesFail(t *testing.T) {

func TestDomainWritePotentialStatesBadID(t *testing.T) {
schema := componentmocks.NewSchema(t)
schemaID := tktypes.Bytes32(tktypes.RandBytes(32))
schemaID := tktypes.RandBytes32()
schema.On("ID").Return(schemaID)
schema.On("Signature").Return("schema1_signature")
td, done := newTestDomain(t, false, goodDomainConf(), mockSchemas(schema), mockBlockHeight)
Expand Down
27 changes: 27 additions & 0 deletions core/go/internal/plugins/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ func (br *domainBridge) RequestReply(ctx context.Context, reqMsg plugintk.Plugin
}
},
)
case *prototk.DomainMessage_SendTransaction:
return callManagerImpl(ctx, req.SendTransaction,
br.manager.SendTransaction,
func(resMsg *prototk.DomainMessage, res *prototk.SendTransactionResponse) {
resMsg.ResponseToDomain = &prototk.DomainMessage_SendTransactionRes{
SendTransactionRes: res,
}
},
)
case *prototk.DomainMessage_LocalNodeName:
return callManagerImpl(ctx, req.LocalNodeName,
br.manager.LocalNodeName,
func(resMsg *prototk.DomainMessage, res *prototk.LocalNodeNameResponse) {
resMsg.ResponseToDomain = &prototk.DomainMessage_LocalNodeNameRes{
LocalNodeNameRes: res,
}
},
)
case *prototk.DomainMessage_GetStates:
return callManagerImpl(ctx, req.GetStates,
br.manager.GetStates,
func(resMsg *prototk.DomainMessage, res *prototk.GetStatesResponse) {
resMsg.ResponseToDomain = &prototk.DomainMessage_GetStatesRes{
GetStatesRes: res,
}
},
)
default:
return nil, i18n.NewError(ctx, msgs.MsgPluginBadRequestBody, req)
}
Expand Down
Loading
Loading