Contract-oriented applications

In this page we develop some simple contract-oriented services, using the middleware APIs via their Java binding.

A simple store

We start with a basic store service, which advertises the contract store2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
String store2 ="?order{;t}.(!price{t<60} + !unavailable{t<10})";
TST c = new TST(store2); 

CO2ServerConnection co2 = new CO2ServerConnection("testuser@co2.unica.it", "pa55w0rd");
Private r = c.toPrivate(co2);
Public  p = r.tell();               //advertises the contract store2

Session s = p.waitForSession();     //blocks until session is created
String id = s.waitForReceive().getStringValue();

if(isAvailable(id)) { 
    s.send("price", getPrice(id)); 
}
else { 
    s.send("unavailable"); 
}

At lines 1-2, the store constructs a TST c for contract store2. At lines 4-5, the store connects to the middleware, providing its credentials. At line 6, the Private object represents the contract in a state where it has not been advertised to the middleware yet. To advertise the contract, we invoke the tell method at line 7. This call returns a Public object, modelling a latent contract that can be “fused” with a compliant one to establish a new session. At line 9, the store waits for a session to be established; the returned Session object allows to interact with a buyer. At line 10, the store waits to receive a message, containing the code of the product requested by the buyer. At lines 12-13, the store sends the message price (with the corresponding value) if the item is available, otherwise it sends unavailable.

A simple buyer

We now show a buyer that can interact with the store. This buyer just accepts the contract store2 that is already published. The contract is identified by its hash, which is obtained from Public.getContractID().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CO2ServerConnection co2 = new CO2ServerConnection(...);

String storeCID = "0x...";
Integer desiredPrice = 10;

Public  p = Public.accept(co2, storeCID, TST.class);
Session s = p.waitForSession();

s.send("order", "11235811");

try {
    Message m = s.waitForReceive();
    switch (m.getLabel()) {
    case "unavailable": break;
    case "price":
        Integer price = Integer.parseInt(m.getStringValue());
        if (price > desiredPrice) {
            /*abort the purchase*/ 
        }
        else {
            /*proceed with the purchase*/
        }
    }
} catch(ContractViolationException e){
    /*The store is culpable*/
}

At line 6, the buyer accepts the store contract, identified by storeCID. The call to Public.accept returns a Public object. At this point a session with the store is already established, and waitForSession just returns the corresponding Session object (line 7). Now, the buyer sends the item code (line 9), waits for the store response (line 12), and finally in the try-catch statement it handles the messages price and unavailable.

Note that the accept primitive allows a participant to establish sessions with a chosen counterpart; instead, this is not allowed by the tell primitive, which can establish a session whenever two contracts are compliant.

A dishonest store

Consider now a more evolved store, which relies on external distributors to retrieve items. As before, the store takes an order from the buyer; however, now it invokes an external distributor if the requested item is not in stock. If the distributor can provide the item, then the store confirms the order to the buyer; otherwise, it informs the buyer that the item is unavailable.

Our first attempt to implement this refined store is the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TST cB = new TST(store2);
TST cD = new TST("!req{;t}.(?ok{t<10} & ?no{t<10})");
		
Public  pB = cB.toPrivate(co2).tell();
Session sB = pB.waitForSession();
String  id = sB.waitForReceive().getStringValue();
	
if (isAvailable(id)) { 
    // handled internally
    sB.send("price", getPrice(id));
}
else { 
    // handled with a distributor
    Public  pD = cD.toPrivate(co2).tell();
    Session sD = pD.waitForSession();

    sD.send("req", id);
    Message mD = sD.waitForReceive();

    switch (mD.getLabel()) {
    case "no" : sB.send("unavailable"); break;
    case "ok" : sB.send("price", getPrice(id)); break;
    }
}

At lines 1-2 we construct two TSTs: cB for interacting with buyers, and cD for interacting with distributors. In cD, the store first sends a request to the distributor for some item, and then waits for an ok or no answer, according to whether the distributor is able to provide the requested item or not. At lines 4-6, the store advertises cB, and it waits for a buyer to join the session; then, it receives the order, and checks if the requested item is in stock (line 8). % If so, the store sends the price of the item to the buyer (line 9).

If the item is not in stock, the store advertises cD to find a distributor (lines 12-13). When a session sD is established, the store forwards the item identifier to the distributor (line 15), and then it waits for a reply. If the reply is no, the store sends unavailable to the buyer, otherwise it sends a price.

Note that this implementation of the store is dishonest, namely it may violate contracts. This happens in the following two cases:

  1. assume that the store has received the buyer order, but the requested item is not in stock. Then, the store advertises the contract cD to find a distributor. Note that there is no guarantee that the session sD will be established within a given deadline, nor that it will be established at all. If more than 60 seconds pass on the waitForSession at line 13, the store becomes culpable with respect to the contract cB. Indeed, such contract requires the store to perform an action before 60 seconds (10 seconds if the action is unavailable).
  2. also if the session sD is timely established, a slow or unresponsive distributor could make the store violate the contract cB. For instance, assume that the distributor sends message no after nearly 10 seconds. In this case, the store may not have enough time to send unavailable to the buyer within 10 seconds, and so it becomes culpable at session sB.

We have simulated a scenario by making the store interact with slow or unresponsive distributors (see the figure below). The experimental results show that, although the store is not culpable in all the sessions, its reputation decreases over time. Recovering from such situation is not straightforward, since the reputation system of the CO2 middleware features defensive techniques against self-promoting attacks.

_images/reputations.png

Reputation of the dishonest and honest stores as a function of the number of sessions with malicious distributors.

An honest store

In order to implement an honest store, we must address the fact that, if the distributor delays its message to the maximum allowed time, the store may not have enough time to respond to the buyer. To cope with this, we adjust the timing constraints in the contract between the store and the distributor, and we implement a revised version of the store as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
TST cB = new TST(store2);
TST cD = new TST("!req{;t} . (?ok{t<5} & ?no{t<5})");

Public  pB = cB.toPrivate(co2).tell();
Session sB = pB.waitForSession();
String  id = sB.waitForReceive().getStringValue();

if (isAvailable(id)) { // handled internally
    sB.send("price", getPrice(id));
}
else { // handled with the distributor
    Public pD = cD.toPrivate(co2).tell(3 * 1000);
    try {
        Session sD = pD.waitForSession();
        sD.send("req", id);

        try{
            Message mD = sD.waitForReceive();

            switch (mD.getLabel()) {
            case "no": sB.send("unavailable"); break;
            case "ok": sB.send("price", getPrice(id)); break;
            }
        } catch(ContractViolationException e){
            //the distributor did not respect its contract
            sB.send("unavailable");
        }
    } catch(ContractExpiredException e) {
        //no distributor found
        sB.send("unavailable");
    }
}

The parameter in the tell at line 12 specifies a deadline of 3 seconds: if the session sD is not established within the deadline, the contract cD is retracted from the middleware, and a ContractExpiredException is thrown. The store catches the exception at line 28, sending unavailable to the buyer.

Instead, if the session sD is established, the store forwards the item identifier to the distributor (line 15), and then waits to receive a response from it. If the distributor does not send neither ok nor no within the deadline specified in cD (5 seconds), the middleware assigns the blame to the distributor for a contract breach, and unblocks the waitForReceive in the store with a ContractViolationException (line 24). In the exception handler, the store fulfils the contract cB by sending unavailable to the buyer.

A recursive honest store

We now present another version of the store, that uses the recursive contract store3 at page~pageref{code:store3}. As in the previous version, if the buyer requests an item that is not in stock, the store resorts to an external distributor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
TST cB = new TST(store3);
TST cD = new TST("!req{;t}.(?ok{t<5} & ?no{t<5})");

Public  pB = cB.toPrivate(co2).tell();
Session sB = pB.waitForSession();
List<String> orders = new ArrayList<>();
Message mB;

try {
    do {
        mB = sB.waitForReceive();
        if (mB.getLabel().equals("addtocart")){
            orders.add(mB.getStringValue()); 
        }
    } while(!mB.getLabel().equals("checkout"));
    
    if (isAvailable(orders)) { // handled internally
        sB.send("price", getPrice(orders));
        String res = sB.waitForReceive().getLabel();
        switch (res){
            case "accept": // handle the order
            case "reject": // terminate
        }
    } 
    else { // handled with the distributor
        Public pD = cD.toPrivate(co2).tell(5 * 1000);
        try {
            Session sD = pD.waitForSession();
            sD.send("req", getOutOfStockItems(orders));
            try{
                switch (sD.waitForReceive().getLabel()) {
                case "no": sB.send("unavailable"); break;
                case "ok":
                    sB.send("price", getPrice(orders));
                    try{
                        String res = sB.waitForReceive().getLabel();
                        switch (res) {
                        case "accept": // handle the order
                        case "reject": // terminate
                        }
                    }
                    catch (ContractViolationException e) {
                        //the buyer is culpable, terminate
                    }
                }
            } catch (ContractViolationException e){
                //the distributor did not respect its contract
                sB.send("unavailable");
            }
        } 
        catch (ContractExpiredException e) {
            //no distributor found
            sB.send("unavailable");
        }
    }
} catch(ContractViolationException e){
    /*the buyer is culpable*/
}

After advertising the contract cB, the store waits for a session sB with the buyer (lines 4-5). After the session is established, the store can receive addtocart multiple times: for each addtocart, it saves the corresponding item identifier in a list. The loop terminates when the buyer selects checkout.

If all requested items are available, the store sends the total price to the buyer (line 18). After that, the store expects either accept or reject from the buyer. If the buyer does not respect his deadlines, an exception is thrown, and it is caught at line 56.

If the buyer replies in time, the store advertises the contract cD, and waits for a session sD with the distributor (lines 26-28). If the session is not established within 5 seconds, an exception is thrown. The store handles the exception at line 51, by sending unavailable to the buyer.

If a session with the distributor is established within the deadline, the store requests the unavailable items, and waits for a response (line 31). If the distributor sends no, the store answers unavailable to the buyer (line 32). If the distributor sends ok, then the interaction between store and buyer proceeds as if the items were in stock. If the distributor does not reply within the deadline, an exception is thrown. The store handles it at line 48, by sending unavailable to the buyer. Also this version of the store respects contracts in all possible contexts, hence it is honest.