Skip to content

Commit

Permalink
feat(client:go): add batch create contract (#689)
Browse files Browse the repository at this point in the history
  • Loading branch information
AbdelrahmanElawady authored May 8, 2023
1 parent 0908148 commit 7ffdc0b
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 0 deletions.
106 changes: 106 additions & 0 deletions clients/tfchain-client-go/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,112 @@ func (s *Substrate) CreateNodeContract(identity Identity, node uint32, body stri
return s.GetContractWithHash(node, h)
}

// BatchCreateContractData struct for batch create contract
type BatchCreateContractData struct {
Node uint32 `json:"node"`
Body string `json:"body"`
Hash string `json:"hash"`
PublicIPs uint32 `json:"public_ips"`
SolutionProviderID *uint64 `json:"solution_provider_id"`

// for name contracts. if set the contract is assumed to be a name contract
// and other fields are ignored
Name string `json:"name"`
}

// BatchAllCreateContract creates a batch of contracts for deployments atomically.
// transaction will rollback on error
func (s *Substrate) BatchAllCreateContract(identity Identity, contractData []BatchCreateContractData) ([]uint64, error) {
contracts, _, err := s.batchCreateContract(identity, contractData, "Utility.batch_all")
return contracts, err
}

// BatchCreateContract creates a batch of contracts for deployments non-atomically.
// on error returns the created contracts, the first failing contract index and the error message.
func (s *Substrate) BatchCreateContract(identity Identity, contractData []BatchCreateContractData) ([]uint64, *int, error) {
return s.batchCreateContract(identity, contractData, "Utility.batch")
}

func (s *Substrate) batchCreateContract(identity Identity, contractData []BatchCreateContractData, batchType string) ([]uint64, *int, error) {
cl, meta, err := s.GetClient()
if err != nil {
return nil, nil, err
}

calls := make([]types.Call, 0)
hexHashes := make([]HexHash, len(contractData))
for i, contract := range contractData {
if contract.Name != "" {
c, err := types.NewCall(meta, "SmartContractModule.create_name_contract",
contract.Name,
)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create call")
}
calls = append(calls, c)
continue
}

var providerID types.OptionU64
if contract.SolutionProviderID != nil {
providerID = types.NewOptionU64(types.U64(*contract.SolutionProviderID))
}

h := NewHexHash(contract.Hash)
c, err := types.NewCall(meta, "SmartContractModule.create_node_contract",
contract.Node, h, contract.Body, contract.PublicIPs, providerID,
)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create call")
}

calls = append(calls, c)
hexHashes[i] = h
}
batchCall, err := types.NewCall(meta, batchType, calls)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create batch call")
}

resp, err := s.Call(cl, meta, identity, batchCall)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create contracts")
}

var failingContract *int
if batchType == "Utility.batch" && resp.Events != nil && resp.Events.Utility_BatchInterrupted != nil {
i := int(resp.Events.Utility_BatchInterrupted[0].Index)
failingContract = &i
}

contracts := make([]uint64, 0)
for i, contract := range contractData {
if failingContract != nil && *failingContract == i {
break
}

var contractID uint64
var err error

if contract.Name != "" {
contractID, err = s.GetContractIDByNameRegistration(contract.Name)
} else {
contractID, err = s.GetContractWithHash(contract.Node, hexHashes[i])
}

if err != nil {
failingContract = &i
return nil, failingContract, err
}
contracts = append(contracts, contractID)
}

if failingContract != nil {
err = errors.New("failed to create contracts")
}
return contracts, failingContract, err
}

// CreateNameContract creates a contract for deployment
func (s *Substrate) CreateNameContract(identity Identity, name string) (uint64, error) {
cl, meta, err := s.GetClient()
Expand Down
145 changes: 145 additions & 0 deletions clients/tfchain-client-go/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,148 @@ func TestCancelBatch(t *testing.T) {
err = cl.BatchCancelContract(identity, []uint64{contractID_1, contractID_2})
require.NoError(t, err)
}

func TestCreateBatch(t *testing.T) {
var nodeID uint32

cl := startLocalConnection(t)
defer cl.Close()

identity, err := NewIdentityFromSr25519Phrase(BobMnemonics)
require.NoError(t, err)

farmID, twinID := assertCreateFarm(t, cl)

nodeID = assertCreateNode(t, cl, farmID, twinID, identity)
require.NoError(t, err)

t.Run("batch all succeeded", func(t *testing.T) {
contracts := []BatchCreateContractData{{
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87255",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Node: nodeID,
Body: "",
Hash: "d20454ba65dfd4577af63d142c622cdc",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Name: "test",
}}

c, err := cl.BatchAllCreateContract(identity, contracts)
require.NoError(t, err)
require.Len(t, c, 3)

_, err = cl.GetContract(c[0])
require.NoError(t, err)

_, err = cl.GetContract(c[1])
require.NoError(t, err)

_, err = cl.GetContract(c[2])
require.NoError(t, err)

err = cl.BatchCancelContract(identity, c)
require.NoError(t, err)
})
t.Run("batch all failed", func(t *testing.T) {
// second contract already exists (same hash)
contracts := []BatchCreateContractData{{
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87250",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87250",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Name: "test",
}}

c, err := cl.BatchAllCreateContract(identity, contracts)
require.Error(t, err)
require.Len(t, c, 0)

hash := NewHexHash(contracts[0].Hash)

// first contract should be rolled back
_, err = cl.GetContractWithHash(nodeID, hash)
require.Error(t, err)
})
t.Run("batch succeeded", func(t *testing.T) {
contracts := []BatchCreateContractData{{
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87251",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Node: nodeID,
Body: "",
Hash: "d20454ba65dfd4577af63d142c622cda",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Name: "test",
}}

c, index, err := cl.BatchCreateContract(identity, contracts)
require.NoError(t, err)
require.Len(t, c, 3)
require.Nil(t, index)

_, err = cl.GetContract(c[0])
require.NoError(t, err)

_, err = cl.GetContract(c[1])
require.NoError(t, err)

_, err = cl.GetContract(c[2])
require.NoError(t, err)

err = cl.BatchCancelContract(identity, c)
require.NoError(t, err)
})

t.Run("batch failed", func(t *testing.T) {
// second contract already exists (same hash)
contracts := []BatchCreateContractData{{
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87253",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Node: nodeID,
Body: "",
Hash: "adf503a0e0160ba74804c27494f87253",
PublicIPs: 0,
SolutionProviderID: nil,
}, {
Name: "test",
}}

c, index, err := cl.BatchCreateContract(identity, contracts)
require.Error(t, err)
require.NotNil(t, index)
require.Equal(t, *index, 1)
require.Len(t, c, 1)

// first contract should be created
_, err = cl.GetContract(c[0])
require.NoError(t, err)

// third contract should not be created
_, err = cl.GetContractIDByNameRegistration(contracts[2].Name)
require.Error(t, err)
})

}

0 comments on commit 7ffdc0b

Please sign in to comment.